SlideShare a Scribd company logo
1 of 49
Download to read offline
Railsā€™ Next Top Model
   Adam Keys, expert typist at Gowalla
   http://therealadam.com
   @therealadam
   RailsConf 2010




Hi, Iā€™m Adam Keys. Iā€™m an expert typist at Gowalla and an amateur language lawyer. Today
Iā€™m going to talk about what I consider the most intriguing part of the reimagination of Rails
that is Rails 3. Namely, I want to explore how ActiveRecord was extracted from itself into
ActiveModel and ActiveRelation.
k s !
                                                         L oo
                                     ea                t
                                 G r
                     u r
         F o

*   Extractions reduce friction in building little languages on top of data stores
*   Reduce the boilerplate code involved in bringing up a data layer
*   Make it easier to add some of the things weā€™ve come to take for granted
*   Allow developers to focus on building better APIs for data
Clean up your domain                            ActiveSupport fanciness
                         objects




*   ActiveSupport, the oft-maligned cake on top of ActiveRecord and Rails are built
*   Smaller and less cumbersome in Rails 3, cherry-pick the functionality you want
*   Use ActiveSupport instead of rolling your own extensions or copy-paste reuse
*   Tighten up your classes by extracting concerns
require 'common'
            require 'active_support/inflector'
            require 'active_support/cache'

            class User

                attr_accessor :name

                def friends
                  cache.fetch("user-#{name}-friends") do
                    %w{ Peter Egon Winston }
                  end
                end

                protected

                def cache
                  ActiveSupport::Cache::MemCacheStore.new
                end

            end

* Not too different from the user model in your own applications
* `cache` is the simplest thing that might work, but could we make it better and cleaner?
require 'active_support/core_ext/class'

             class User
               cattr_accessor :cache
               attr_accessor :name

                def friends
                  cache.fetch("user-#{name}-friends") do
                    %w{ Peter Egon Winston }
                  end
                end

             end




* Use a class attribute to get the cache conļ¬guration out of the instance
* Could use the inheritable version if we are building our own framework
User.cache = ActiveSupport::Cache::MemCacheStore.new




* In our application setup, create a cache instance and assign it to whatever classes need it
def friends
                   cache.fetch("user-#{name}-friends") do
                     %w{ Peter Egon Winston }
                   end
                 end




* Suppose weā€™re going to end up with a lot of methods that look like this
* Thereā€™s a lot of potential boiler-plate code to write there
* Is there a way we can isolate specify a name, key format, and the logic to use?
cache_key(:friends, :friends_key) do
                  %w{ Peter Egon Winston }
                end

                def friends_key
                  "user-#{name}-friends"
                end




* I like to start by thinking what the little language will look like
* From there, I start adding the code to make it go
* Hat tip, Rich Kilmer
cattr_accessor :cache_lookups, :cache_keys do
              {}
            end

            def self.cache_key(name, key, &block)
              class_eval %Q{
                cache_lookups[name] = block
                cache_keys[name] = key

                  def #{name}
                    return @#{name} if @#{name}.present?
                    key = method(cache_keys[:#{name}]).call
                    @#{name} = cache.fetch(key) do
                      block.call
                    end
                  end
              }
            end




*   Add a couple class attributes to keep track of things, this time with default values
*   Write a class method that adds a method for each cache key we add
*   Look up the the cache key to fetch from, look up the body to call to populate it, off we go
*   The catch: block is bound to class rather than instance
class User
                    cattr_accessor :cache
                    attr_accessor :name

                    cattr_accessor :cache_lookups, :cache_keys do
                      {}
                    end

                    def self.cache_key(name, key, &block)
                      class_eval %Q{
                        cache_lookups[name] = block
                        cache_keys[name] = key

                          def #{name}
                            return @#{name} if @#{name}.present?
                            key = method(cache_keys[:#{name}]).call
                            @#{name} = cache.fetch(key) do
                              block.call
                            end
                          end
                      }
                    end

                    cache_key(:friends, :friends_key) do
                      %w{ Peter Egon Winston }
                    end

                    def friends_key
                      "user-#{name}-friends"
                    end

                  end



* Downside: now our class wonā€™t ļ¬t nicely on one slide; is this a smell?
* ActiveSupport enables a nice little refactoring Iā€™ve started calling ā€œextract concernā€
class User
                    cattr_accessor :cache
                    attr_accessor :name

                    cattr_accessor :cache_lookups, :cache_keys do
                      {}
                    end

                    def self.cache_key(name, key, &block)
                      class_eval %Q{
                        cache_lookups[name] = block
                        cache_keys[name] = key

                          def #{name}
                            return @#{name} if @#{name}.present?
                            key = method(cache_keys[:#{name}]).call
                            @#{name} = cache.fetch(key) do
                              block.call
                            end
                          end
                      }
                    end

                    cache_key(:friends, :friends_key) do
                      %w{ Peter Egon Winston }
                    end

                    def friends_key
                      "user-#{name}-friends"
                    end

                  end



* Downside: now our class wonā€™t ļ¬t nicely on one slide; is this a smell?
* ActiveSupport enables a nice little refactoring Iā€™ve started calling ā€œextract concernā€
require 'active_support/concern'

           module Cacheabilly
             extend ActiveSupport::Concern

             included do
               cattr_accessor :cache

                cattr_accessor :cache_lookups, :cache_keys do
                  {}
                end

                def self.cache_key(name, key, &block)
                  cache_lookups[name] = block
                  cache_keys[name] = key

                  class_eval %Q{

                         def #{name}
                           return @#{name} if @#{name}.present?
                           key = method(cache_keys[:#{name}]).call
                           @#{name} = cache.fetch(key) do
                             block.call
                           end
                         end
                     }
               end
             end
           end

* We pick up all the machinery involved in making `cache_key` work and move into a module
* Then we wrap that bit in the included hook and extend ActiveSupport::Concern
* Easier to read than the old convention of modules included in, ala plugins
class User
              include Cacheabilly

               attr_accessor :name

               cache_key(:friends, :friends_key) do
                 %w{ Peter Egon Winston }
               end

               def friends_key
                 "user-#{name}-friends"
               end

            end




* Domain object ļ¬ts on one slide again
* Easy to see where the cache behavior comes from
Accessors + concerns = slimming effect




*   ActiveSupport can help remove tedious code from your logic
*   ActiveSupport can make your classes simpler to reason about
*   Also look out for handy helper classes like MessageVeriļ¬er/Encryper, SecureRandom, etc.
*   Give it a fresh look, even if itā€™s previously stabbed you in the face
Models that look good                               ActiveModel validations
     and want to talk good too




* ActiveModel is the result of extracting much of the goodness of ActiveRecord
* If youā€™ve ever wanted validations, callbacks, dirty tracking, or serialization, this is your jam
* Better still, ActiveModel is cherry-pickable like ActiveSupport
include ActiveModel::Validations

  validates_presence_of :name
  validates_length_of :name,
    :minimum => 3,
    :message => 'Names with less than 3 characters are dumb'




* Adding validations to our user model is easy
* These are one in the same with what youā€™re using in AR
* No methods needed to get this functionality; just include and youā€™re on your way
class GhostbusterValidator < ActiveModel::Validator

       def validate(record)
         names = %w{ Peter Ray Egon Winston }
         return if names.include?(record.name)
         record.errors[:base] << "Not a Ghostbuster :("
       end

    end




* With ActiveModel, we can also implement validation logic in external classes
* Nice for sharing between projects or extracting involved validation
validates_with GhostbusterValidator




* Step 1: specify your validation class
* There is no step 2
class User
            include Cacheabilly

             attr_accessor :name

             cache_key(:friends, :friends_key) do
               %w{ Peter Egon Winston }
             end

             def friends_key
               "user-#{name}-friends"
             end

             include ActiveModel::Validations

            validates_presence_of :name
            validates_length_of :name,
              :minimum => 3,
              :message => 'Names with less than 3
          characters are dumb'
            validates_with GhostbusterValidator

          end
* Now our class looks like this
* Still ļ¬ts on one slide
class User
            include Cacheabilly

             attr_accessor :name

             cache_key(:friends, :friends_key) do
               %w{ Peter Egon Winston }
             end

             def friends_key
               "user-#{name}-friends"
             end

             include ActiveModel::Validations

            validates_presence_of :name
            validates_length_of :name,
              :minimum => 3,
              :message => 'Names with less than 3
          characters are dumb'
            validates_with GhostbusterValidator

          end
* Now our class looks like this
* Still ļ¬ts on one slide
>> u = User.new
 => #<User:0x103a56f28>
 >> u.valid?
 => false
 >> u.errors
 => #<OrderedHash {:base=>["Not a Ghostbuster :("], :name=>["can't be
 blank", "can't be blank", "Names with less than 3 characters are
 dumb", "can't be blank", "Names with less than 3 characters are
 dumb"]}>




Using the validations, no surprise, looks just like AR
>>   u.name = 'Ron'
  =>   "Ron"
  >>   u.valid?
  =>   false
  >>   u.errors
  =>   #<OrderedHash {:base=>["Not a Ghostbuster :("]}>




Ron Evans is a cool dude, but heā€™s no Ghostbuster
>>   u.name = 'Ray'
       =>   "Ray"
       >>   u.valid?
       =>   true




Ray is a bonaļ¬de Ghostbuster
Serialize your objects,                            ActiveModel lifecycle helpers
                       your way




* Validations are cool and easy
* What if we want to encode our object as JSON or XML
* Tyra is not so sure she wants to write that code herself
attr_accessor :degree, :thought




Letā€™s add a couple more attributes to our class, for grins.
def attributes
        @attributes ||= {'name' => name,
                         'degree' => degree,
                         'thought' => thought}
      end

      def attributes=(hash)
        self.name = hash['name']
        self.degree = hash['degree']
        self.thought = hash['thought']
      end




* If we add `attributes`, AMo knows what attributes to serialize when it encodes your object
* If we implement `attributes=`, we can specify how a serialized object gets decoded
include ActiveModel::Serializers::JSON
                include ActiveModel::Serializers::Xml




Once thatā€™s done, we pull in the serializers we want to make available.
>> u.serializable_hash
=> {"name"=>"Ray Stanz", "degree"=>"Parapsychology", "thought"=>"The
Stay-Puft Marshmallow Man"}
>> u.to_json
=> "{"name":"Ray Stanz","degree":"Parapsychology","thought
":"The Stay-Puft Marshmallow Man"}"
>> u.to_xml
=> "<?xml version="1.0" encoding="UTF-8"?>n<user>n
<degree>Parapsychology</degree>n <name>Ray Stanz</name>n
<thought>The Stay-Puft Marshmallow Man</thought>n</user>n"




* We get a serializable hash method which is what gets encoded
* `to_json` and `to_xml` are now ours, just like with AR
>> json = u.to_json
=> "{"name":"Ray Stanz","degree":"Parapsychology","thought
":"The Stay-Puft Marshmallow Man"}"
>> new_user = User.new
=> #<User:0x103166378>
>> new_user.from_json(json)
=> #<User:0x103166378 @name="Ray Stanz", @degree="Parapsychology",
@thought="The Stay-Puft Marshmallow Man">




We can even use `from_json` and `from_xml`!
Persistence and queries,                               ActiveRelation persistence
                  like a boss




* In Rails 2, AR contains a bunch of logic for banging string together to form queries
* In Rails 3, thatā€™s been abstracted into a library that models the relational algebra that
databases use
* But, Arel makes it possible to use the same API to query all your data sources
include Arel::Relation

                              cattr_accessor :engine




* To make our class query and persist like an AR object, we need to include `Relation`
* Weā€™ll also need an engine, which weā€™ll look into soonly
* Including Relation gives us a whole bunch of methods that look familiar from AR: where,
order, take, skip, and some that are more SQLlish: insert, update, delete
def save
                      insert(self)
                    end

                    def find(name)
                      key = name.downcase.gsub(' ', '_')
                      where("user-#{key}").call
                    end




* Since our goal is to be somewhat like AR, weā€™ll add some sugar on top of the Relation
* Our save isnā€™t as clever as ARā€™s, in that it only creates records, but we could add dirty
tracking later
* Find is just sugar on top of `where`, which is quite similar to how we use it in AR
def marshal_dump
                              attributes
                            end

                            def marshal_load(hash)
                              self.attributes = hash
                            end




* For those following along at home, weā€™ll need these on User too, due to some oddness
between AMoā€™s serialization and Arelā€™s each method
* Ideally, weā€™d serialize with JSON instead, but this gets the job done for now
class UserEngine

                               attr_reader :cache

                               def initialize(cache)
                                 @cache = cache
                               end

                            end




* Arel implements the query mechanism, but you still need to write an ā€œengineā€ to handle
translating to the right query language and reading/writing
* These seem to be called engines by convention, but they are basically just a duck type
* The methods weā€™ll need to implement are our good CRUD friends
def create(insert)
                      record = insert.relation
                      key = record.cache_key
                      value = record
                      cache.write(key, value)
                    end




*   Getting these engines up is mostly a matter of grokking what is in an ARel relation
*   Everything is passed a relation
*   The insert object has a relation that represents the record we want to create
*   Uses Marshal, JSON or YAML would be nicer
def read(select)
     raise ArgumentError.new("#{select.class} not
       supported") unless select.is_a?(Arel::Where)
     key = select.predicates.first.value
     cache.read(key)
   end




* Reads are where we query the datastore
* The select object contains the last method in whatever query chain we called
* Our memcached-based gizmo only supports `where` but we could get a `project`, `take`,
`order` etc.
* Spend lots of time poking the insides of these various objects to grab the data you need to
construct a query
def update(update)
                record = update.assignments.value
                key = record.cache_key
                value = record
                cache.write(key, value)
              end




* Update objects contain an assignment, which has the record weā€™re after
* Again, uses Marshal, which is suboptimal
def delete(delete)
                      key = delete.relation.cache_key
                      cache.delete(key)
                    end




* Delete passes the relation weā€™re going to remove; not much going on here
class UserEngine

        attr_reader :cache

        def initialize(cache)
          @cache = cache
        end

        def create(insert)
          record = insert.relation
          key = record.cache_key
          value = record
          cache.write(key, value)
        end

        def read(select)
          raise ArgumentError.new("#{select.class} not supported") unless select.is_a?
      (Arel::Where)
          key = select.predicates.first.value
          cache.read(key)
        end

        def update(update)
          record = relation.assignments.value
          key = record.cache_key
          value = record
          cache.write(key, value)
        end

        def delete(delete)
          key = delete.relation.cache_key
          cache.delete(key)
        end

      end


* The entirety of our engine
* Use this as a starting point for your datastore; itā€™s probably not entirely right for what you
want to do, but itā€™s better than trying to ļ¬gure things out from scratch
* Read the in-memory engine that comes with ARel or look at arel-mongo
User.cache = ActiveSupport::Cache::MemCacheStore.new
    User.engine = UserEngine.new(User.cache)




Hereā€™s how we set up our engine
# >> u = User.new
    # => #<User:0x103655eb0>
    # >> u.name = 'Ray Stanz'
    # => "Ray Stanz"
    # >> u.degree = 'Parapsychology'
    # => "Parapsychology"
    # >> u.thought = 'The Stay-Puft Marshmallow Man'
    # => "The Stay-Puft Marshmallow Man"
    # >> u.save
    # => true
    # >> other = User.new
    # => #<User:0x103643b20>
    # >> user.find('Ray Stanz')
    # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology",
    @thought="The Stay-Puft Marshmallow Man">
    # >> user.thought = ''
    # => ""
    # >> user.update(user)
    # => true
    # >> user.delete
    # => true



*   Create a user object
*   Save it to the cache
*   Read it back out
*   Update it
*   Delete it
# >> u = User.new
    # => #<User:0x103655eb0>
    # >> u.name = 'Ray Stanz'
    # => "Ray Stanz"
    # >> u.degree = 'Parapsychology'
    # => "Parapsychology"
    # >> u.thought = 'The Stay-Puft Marshmallow Man'
    # => "The Stay-Puft Marshmallow Man"
    # >> u.save
    # => true
    # >> other = User.new
    # => #<User:0x103643b20>
    # >> user.find('Ray Stanz')
    # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology",
    @thought="The Stay-Puft Marshmallow Man">
    # >> user.thought = ''
    # => ""
    # >> user.update(user)
    # => true
    # >> user.delete
    # => true



*   Create a user object
*   Save it to the cache
*   Read it back out
*   Update it
*   Delete it
# >> u = User.new
    # => #<User:0x103655eb0>
    # >> u.name = 'Ray Stanz'
    # => "Ray Stanz"
    # >> u.degree = 'Parapsychology'
    # => "Parapsychology"
    # >> u.thought = 'The Stay-Puft Marshmallow Man'
    # => "The Stay-Puft Marshmallow Man"
    # >> u.save
    # => true
    # >> other = User.new
    # => #<User:0x103643b20>
    # >> user.find('Ray Stanz')
    # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology",
    @thought="The Stay-Puft Marshmallow Man">
    # >> user.thought = ''
    # => ""
    # >> user.update(user)
    # => true
    # >> user.delete
    # => true



*   Create a user object
*   Save it to the cache
*   Read it back out
*   Update it
*   Delete it
~15 collars, BTW


* One dude, one pair of shades, one ļ¬‚eshbeard, ļ¬fteen collars
* Weā€™ve popped a lot of collars, but we got a lot of functionality too
class User
  include Cacheabilly

 attr_accessor :name

 cache_key(:friends, :friends_key) do
   %w{ Peter Egon Winston }
 end

 def friends_key
   "user-#{name}-friends"
 end

 include ActiveModel::Validations

  validates_presence_of :name
  validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb'
  validates_with GhostbusterValidator

 include ActiveModel::Serialization

 attr_accessor :degree, :thought

 def attributes
   @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought}
 end

 def attributes=(hash)
   self.name = hash['name']
   self.degree = hash['degree']
   self.thought = hash['thought']
 end

 include ActiveModel::Serializers::JSON
 include ActiveModel::Serializers::Xml

 include Arel::Relation

  cattr_accessor :engine

 # Our engine uses this method to infer the record's key
 def cache_key
   "user-#{name.downcase.gsub(' ', '_')}"
 end

 def marshal_dump
   attributes
 end

 def marshal_load(hash)
   self.attributes = hash
 end

 def save
   # HAX: use dirty tracking to call insert or update here
   insert(self)
 end

 def find(name)
   key = name.downcase.gsub(' ', '_')
   where("user-#{key}").call
 end

end




* Hereā€™s our domain model and hereā€™s all the support code
* What we get: declarative lazy caching, validations, serialization, persistence, querying
require   'common'
                                                                                                       require   'active_support/concern'
                                                                                                       require   'active_support/core_ext/class'
                                                                                                       require   'active_support/inflector'
                                                                                                       require   'active_support/cache'
                                                                                                       require   'active_model'
                                                                                                       require   'arel'

                                                                                                       module Cacheabilly
                                                                                                         extend ActiveSupport::Concern

                                                                                                         included do
                                                                                                           cattr_accessor :cache
class User
  include Cacheabilly
                                                                                                             cattr_accessor :cache_lookups, :cache_keys do
                                                                                                               {}
 attr_accessor :name
                                                                                                             end
 cache_key(:friends, :friends_key) do
                                                                                                             def self.cache_key(name, key, &block)
   %w{ Peter Egon Winston }
                                                                                                               cache_lookups[name] = block
 end
                                                                                                               cache_keys[name] = key
 def friends_key
                                                                                                               class_eval %Q{
   "user-#{name}-friends"
 end
                                                                                                                     def #{name}
                                                                                                                       return @#{name} if @#{name}.present?
 include ActiveModel::Validations
                                                                                                                       key = method(cache_keys[:#{name}]).call
                                                                                                                       @#{name} = cache.fetch(key) do
  validates_presence_of :name
                                                                                                                         block.call
  validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb'
                                                                                                                       end
  validates_with GhostbusterValidator
                                                                                                                     end
                                                                                                                 }
 include ActiveModel::Serialization
                                                                                                           end
                                                                                                         end
 attr_accessor :degree, :thought
                                                                                                       end
 def attributes
                                                                                                       class GhostbusterValidator < ActiveModel::Validator
   @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought}
 end
                                                                                                        def validate(record)
                                                                                                          return if %w{ Peter Ray Egon Winston }.include?(record.name)
 def attributes=(hash)
                                                                                                          record.errors[:base] << "Not a Ghostbuster :("
   self.name = hash['name']
                                                                                                        end
   self.degree = hash['degree']
   self.thought = hash['thought']
                                                                                                       end
 end

 include ActiveModel::Serializers::JSON
 include ActiveModel::Serializers::Xml

 include Arel::Relation

  cattr_accessor :engine

 # Our engine uses this method to infer the record's key
 def cache_key
   "user-#{name.downcase.gsub(' ', '_')}"
 end

 def marshal_dump
   attributes
 end

 def marshal_load(hash)
   self.attributes = hash
 end

 def save
   # HAX: use dirty tracking to call insert or update here
   insert(self)
 end

 def find(name)
   key = name.downcase.gsub(' ', '_')
   where("user-#{key}").call
 end

end




* Hereā€™s our domain model and hereā€™s all the support code
* What we get: declarative lazy caching, validations, serialization, persistence, querying
require   'common'
                                                                                                       require   'active_support/concern'
                                                                                                       require   'active_support/core_ext/class'
                                                                                                       require   'active_support/inflector'
                                                                                                       require   'active_support/cache'
                                                                                                       require   'active_model'
                                                                                                       require   'arel'

                                                                                                       module Cacheabilly
                                                                                                         extend ActiveSupport::Concern

                                                                                                         included do
                                                                                                           cattr_accessor :cache
class User
  include Cacheabilly
                                                                                                             cattr_accessor :cache_lookups, :cache_keys do
                                                                                                               {}
 attr_accessor :name
                                                                                                             end
 cache_key(:friends, :friends_key) do
                                                                                                             def self.cache_key(name, key, &block)
   %w{ Peter Egon Winston }
                                                                                                               cache_lookups[name] = block
 end
                                                                                                               cache_keys[name] = key
 def friends_key
                                                                                                               class_eval %Q{
   "user-#{name}-friends"
 end
                                                                                                                     def #{name}
                                                                                                                       return @#{name} if @#{name}.present?
 include ActiveModel::Validations
                                                                                                                       key = method(cache_keys[:#{name}]).call
                                                                                                                       @#{name} = cache.fetch(key) do
  validates_presence_of :name
                                                                                                                         block.call
  validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb'
                                                                                                                       end
  validates_with GhostbusterValidator
                                                                                                                     end
                                                                                                                 }
 include ActiveModel::Serialization
                                                                                                           end
                                                                                                         end
 attr_accessor :degree, :thought
                                                                                                       end
 def attributes
                                                                                                       class GhostbusterValidator < ActiveModel::Validator
   @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought}
 end
                                                                                                         def validate(record)
                                                                                                           return if %w{ Peter Ray Egon Winston }.include?(record.name)
 def attributes=(hash)
                                                                                                           record.errors[:base] << "Not a Ghostbuster :("
   self.name = hash['name']
                                                                                                         end
   self.degree = hash['degree']
   self.thought = hash['thought']
                                                                                                       end
 end
                                                                                                       class UserEngine
 include ActiveModel::Serializers::JSON
 include ActiveModel::Serializers::Xml
                                                                                                         attr_reader :cache
 include Arel::Relation
                                                                                                         def initialize(cache)
                                                                                                           @cache = cache
  cattr_accessor :engine
                                                                                                         end
 # Our engine uses this method to infer the record's key
                                                                                                         def create(insert)
 def cache_key
                                                                                                           record = insert.relation
   "user-#{name.downcase.gsub(' ', '_')}"
                                                                                                           key = record.cache_key
 end
                                                                                                           value = record # Note: this uses Marshal, b/c to_json w/ arel is buggy
                                                                                                           cache.write(key, value)
 def marshal_dump
                                                                                                         end
   attributes
 end
                                                                                                         # Ignores chained queries, i.e. take(n).where(...)
                                                                                                         def read(select)
 def marshal_load(hash)
                                                                                                           raise ArgumentError.new("#{select.class} not supported") unless select.is_a?(Arel::Where)
   self.attributes = hash
                                                                                                           key = select.predicates.first.value
 end
                                                                                                           cache.read(key)
                                                                                                         end
 def save
   # HAX: use dirty tracking to call insert or update here
                                                                                                         def update(update)
   insert(self)
                                                                                                           record = relation.assignments.value
 end
                                                                                                           key = record.cache_key
                                                                                                           value = record
 def find(name)
                                                                                                           cache.write(key, value)
   key = name.downcase.gsub(' ', '_')
                                                                                                         end
   where("user-#{key}").call
 end
                                                                                                         def delete(delete)
                                                                                                           key = delete.relation.cache_key
end
                                                                                                           cache.delete(key)
                                                                                                         end

                                                                                                       end

                                                                                                       User.cache = ActiveSupport::Cache::MemCacheStore.new('localhost')
                                                                                                       User.engine = UserEngine.new(User.cache)




* Hereā€™s our domain model and hereā€™s all the support code
* What we get: declarative lazy caching, validations, serialization, persistence, querying
ā€œKeysā€ to success
ą¹ give ActiveSupport a try
ą¹ fancy up your classes with AMo

ą¹ build data layers with ARel

ą¹ make better codes
Thanks!




I hope you all got something out of this talk. Iā€™ll post the slides and example code on my
website, therealadam.com, soon. Thanks for coming!

More Related Content

What's hot

Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
Ā 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Jeff Carouth
Ā 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Fabien Potencier
Ā 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkG Woo
Ā 
Node.js in action
Node.js in actionNode.js in action
Node.js in actionSimon Su
Ā 
Drupal 8 Sample Module
Drupal 8 Sample ModuleDrupal 8 Sample Module
Drupal 8 Sample Moduledrubb
Ā 
Caching and Scaling WordPress using Fragment Caching
Caching and Scaling WordPress using Fragment CachingCaching and Scaling WordPress using Fragment Caching
Caching and Scaling WordPress using Fragment CachingErick Hitter
Ā 
RubyEnRails2007 - Dr Nic Williams - DIY Syntax
RubyEnRails2007 - Dr Nic Williams - DIY SyntaxRubyEnRails2007 - Dr Nic Williams - DIY Syntax
RubyEnRails2007 - Dr Nic Williams - DIY SyntaxDr Nic Williams
Ā 
Dependency Injection in Laravel
Dependency Injection in LaravelDependency Injection in Laravel
Dependency Injection in LaravelHAO-WEN ZHANG
Ā 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksNate Abele
Ā 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
Ā 
PHP Data Objects
PHP Data ObjectsPHP Data Objects
PHP Data ObjectsWez Furlong
Ā 
Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8PrinceGuru MS
Ā 
RubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteRubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteDr Nic Williams
Ā 
Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Fabien Potencier
Ā 
The art of readable code (ch1~ch4)
The art of readable code (ch1~ch4)The art of readable code (ch1~ch4)
The art of readable code (ch1~ch4)Ki Sung Bae
Ā 
Drupal 8: Routing & More
Drupal 8: Routing & MoreDrupal 8: Routing & More
Drupal 8: Routing & Moredrubb
Ā 
IntroducciĆ³n rĆ”pida a SQL
IntroducciĆ³n rĆ”pida a SQLIntroducciĆ³n rĆ”pida a SQL
IntroducciĆ³n rĆ”pida a SQLCarlos Hernando
Ā 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128PrinceGuru MS
Ā 

What's hot (19)

Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
Ā 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
Ā 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010
Ā 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
Ā 
Node.js in action
Node.js in actionNode.js in action
Node.js in action
Ā 
Drupal 8 Sample Module
Drupal 8 Sample ModuleDrupal 8 Sample Module
Drupal 8 Sample Module
Ā 
Caching and Scaling WordPress using Fragment Caching
Caching and Scaling WordPress using Fragment CachingCaching and Scaling WordPress using Fragment Caching
Caching and Scaling WordPress using Fragment Caching
Ā 
RubyEnRails2007 - Dr Nic Williams - DIY Syntax
RubyEnRails2007 - Dr Nic Williams - DIY SyntaxRubyEnRails2007 - Dr Nic Williams - DIY Syntax
RubyEnRails2007 - Dr Nic Williams - DIY Syntax
Ā 
Dependency Injection in Laravel
Dependency Injection in LaravelDependency Injection in Laravel
Dependency Injection in Laravel
Ā 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
Ā 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
Ā 
PHP Data Objects
PHP Data ObjectsPHP Data Objects
PHP Data Objects
Ā 
Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8
Ā 
RubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - KeynoteRubyEnRails2007 - Dr Nic Williams - Keynote
RubyEnRails2007 - Dr Nic Williams - Keynote
Ā 
Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2Unit and Functional Testing with Symfony2
Unit and Functional Testing with Symfony2
Ā 
The art of readable code (ch1~ch4)
The art of readable code (ch1~ch4)The art of readable code (ch1~ch4)
The art of readable code (ch1~ch4)
Ā 
Drupal 8: Routing & More
Drupal 8: Routing & MoreDrupal 8: Routing & More
Drupal 8: Routing & More
Ā 
IntroducciĆ³n rĆ”pida a SQL
IntroducciĆ³n rĆ”pida a SQLIntroducciĆ³n rĆ”pida a SQL
IntroducciĆ³n rĆ”pida a SQL
Ā 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128
Ā 

Viewers also liked

Bicycle Tire Installation
Bicycle Tire InstallationBicycle Tire Installation
Bicycle Tire InstallationAngela Dresselhaus
Ā 
5esercitazionejetmarket
5esercitazionejetmarket5esercitazionejetmarket
5esercitazionejetmarketguestab3173
Ā 
Discovery and Application of Voyager's New Complex Publication Patterns
Discovery and Application of Voyager's New Complex Publication PatternsDiscovery and Application of Voyager's New Complex Publication Patterns
Discovery and Application of Voyager's New Complex Publication PatternsAngela Dresselhaus
Ā 
Make More Awesome
Make More AwesomeMake More Awesome
Make More AwesomeAdam Keys
Ā 
Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic RevisitedAdam Keys
Ā 
4flslides
4flslides4flslides
4flslidesAndy Weiss
Ā 
Geography 10_period 6
Geography 10_period 6Geography 10_period 6
Geography 10_period 6kiyoshi
Ā 
On Presenting
On PresentingOn Presenting
On PresentingAdam Keys
Ā 
Geography 10_period 7
Geography 10_period 7Geography 10_period 7
Geography 10_period 7kiyoshi
Ā 
The Holistic Programmer
The Holistic ProgrammerThe Holistic Programmer
The Holistic ProgrammerAdam Keys
Ā 
People Hacks
People HacksPeople Hacks
People HacksAdam Keys
Ā 
El Internet
El InternetEl Internet
El InternetHeli Lazaro
Ā 
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回Hikaru GOTO
Ā 
Practica Excel: Registro de Asistencia y Notas
Practica Excel: Registro de Asistencia y NotasPractica Excel: Registro de Asistencia y Notas
Practica Excel: Registro de Asistencia y NotasHeli Lazaro
Ā 
Microsoft Word: configuraciones BƔsicas
Microsoft Word: configuraciones BƔsicasMicrosoft Word: configuraciones BƔsicas
Microsoft Word: configuraciones BƔsicasHeli Lazaro
Ā 

Viewers also liked (18)

Bicycle Tire Installation
Bicycle Tire InstallationBicycle Tire Installation
Bicycle Tire Installation
Ā 
5esercitazionejetmarket
5esercitazionejetmarket5esercitazionejetmarket
5esercitazionejetmarket
Ā 
Discovery and Application of Voyager's New Complex Publication Patterns
Discovery and Application of Voyager's New Complex Publication PatternsDiscovery and Application of Voyager's New Complex Publication Patterns
Discovery and Application of Voyager's New Complex Publication Patterns
Ā 
Peak Oil Searches
Peak Oil SearchesPeak Oil Searches
Peak Oil Searches
Ā 
Angela Dresselhaus
Angela DresselhausAngela Dresselhaus
Angela Dresselhaus
Ā 
Make More Awesome
Make More AwesomeMake More Awesome
Make More Awesome
Ā 
Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic Revisited
Ā 
4flslides
4flslides4flslides
4flslides
Ā 
Geography 10_period 6
Geography 10_period 6Geography 10_period 6
Geography 10_period 6
Ā 
On Presenting
On PresentingOn Presenting
On Presenting
Ā 
Geography 10_period 7
Geography 10_period 7Geography 10_period 7
Geography 10_period 7
Ā 
The Holistic Programmer
The Holistic ProgrammerThe Holistic Programmer
The Holistic Programmer
Ā 
People Hacks
People HacksPeople Hacks
People Hacks
Ā 
El Internet
El InternetEl Internet
El Internet
Ā 
Cataloging Presentation
Cataloging PresentationCataloging Presentation
Cataloging Presentation
Ā 
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回
ēµ±č؈ēš„å› ęžœęŽØč«–å‹‰å¼·ä¼šć€€ē¬¬1回
Ā 
Practica Excel: Registro de Asistencia y Notas
Practica Excel: Registro de Asistencia y NotasPractica Excel: Registro de Asistencia y Notas
Practica Excel: Registro de Asistencia y Notas
Ā 
Microsoft Word: configuraciones BƔsicas
Microsoft Word: configuraciones BƔsicasMicrosoft Word: configuraciones BƔsicas
Microsoft Word: configuraciones BƔsicas
Ā 

Similar to Rails' Next Top Model

Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Fabien Potencier
Ā 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkBen Scofield
Ā 
MUC - Moodle Universal Cache
MUC - Moodle Universal CacheMUC - Moodle Universal Cache
MUC - Moodle Universal CacheTim Hunt
Ā 
Dependency injection - phpday 2010
Dependency injection - phpday 2010Dependency injection - phpday 2010
Dependency injection - phpday 2010Fabien Potencier
Ā 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201Fabien Potencier
Ā 
Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Fabien Potencier
Ā 
Dependency Injection
Dependency InjectionDependency Injection
Dependency InjectionFabien Potencier
Ā 
Cocoa for Web Developers
Cocoa for Web DevelopersCocoa for Web Developers
Cocoa for Web Developersgeorgebrock
Ā 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8Alexei Gorobets
Ā 
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for JavaHave Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for JavaHoward Lewis Ship
Ā 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial EnAnkur Dongre
Ā 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial EnAnkur Dongre
Ā 
Django Good Practices
Django Good PracticesDjango Good Practices
Django Good PracticesSolution4Future
Ā 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionNate Abele
Ā 
eZ Publish Cluster Unleashed
eZ Publish Cluster UnleashedeZ Publish Cluster Unleashed
eZ Publish Cluster UnleashedBertrand Dunogier
Ā 
Php object orientation and classes
Php object orientation and classesPhp object orientation and classes
Php object orientation and classesKumar
Ā 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1Jano Suchal
Ā 
eZ Publish cluster unleashed revisited
eZ Publish cluster unleashed revisitedeZ Publish cluster unleashed revisited
eZ Publish cluster unleashed revisitedBertrand Dunogier
Ā 

Similar to Rails' Next Top Model (20)

Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3Dependency Injection with PHP 5.3
Dependency Injection with PHP 5.3
Ā 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web Framework
Ā 
MUC - Moodle Universal Cache
MUC - Moodle Universal CacheMUC - Moodle Universal Cache
MUC - Moodle Universal Cache
Ā 
Dependency injection - phpday 2010
Dependency injection - phpday 2010Dependency injection - phpday 2010
Dependency injection - phpday 2010
Ā 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201
Ā 
Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010Dependency Injection - ConFoo 2010
Dependency Injection - ConFoo 2010
Ā 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
Ā 
Cocoa for Web Developers
Cocoa for Web DevelopersCocoa for Web Developers
Cocoa for Web Developers
Ā 
Ruby tricks2
Ruby tricks2Ruby tricks2
Ruby tricks2
Ā 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
Ā 
Objective C Memory Management
Objective C Memory ManagementObjective C Memory Management
Objective C Memory Management
Ā 
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for JavaHave Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Ā 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial En
Ā 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial En
Ā 
Django Good Practices
Django Good PracticesDjango Good Practices
Django Good Practices
Ā 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Ā 
eZ Publish Cluster Unleashed
eZ Publish Cluster UnleashedeZ Publish Cluster Unleashed
eZ Publish Cluster Unleashed
Ā 
Php object orientation and classes
Php object orientation and classesPhp object orientation and classes
Php object orientation and classes
Ā 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1
Ā 
eZ Publish cluster unleashed revisited
eZ Publish cluster unleashed revisitedeZ Publish cluster unleashed revisited
eZ Publish cluster unleashed revisited
Ā 

Recently uploaded

Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
Ā 
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | DelhiFULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhisoniya singh
Ā 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
Ā 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
Ā 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
Ā 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
Ā 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
Ā 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
Ā 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
Ā 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
Ā 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
Ā 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...gurkirankumar98700
Ā 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
Ā 
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...Alan Dix
Ā 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
Ā 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
Ā 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
Ā 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
Ā 
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024BookNet Canada
Ā 
Finology Group ā€“ Insurtech Innovation Award 2024
Finology Group ā€“ Insurtech Innovation Award 2024Finology Group ā€“ Insurtech Innovation Award 2024
Finology Group ā€“ Insurtech Innovation Award 2024The Digital Insurer
Ā 

Recently uploaded (20)

Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
Ā 
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | DelhiFULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
Ā 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
Ā 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Ā 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
Ā 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
Ā 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
Ā 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
Ā 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
Ā 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
Ā 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
Ā 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service šŸø 8923113531 šŸŽ° Avail...
Ā 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
Ā 
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...
Swan(sea) Song ā€“ personal research during my six years at Swansea ... and bey...
Ā 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
Ā 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
Ā 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Ā 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
Ā 
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: Whatā€™s new for BISAC - Tech Forum 2024
Ā 
Finology Group ā€“ Insurtech Innovation Award 2024
Finology Group ā€“ Insurtech Innovation Award 2024Finology Group ā€“ Insurtech Innovation Award 2024
Finology Group ā€“ Insurtech Innovation Award 2024
Ā 

Rails' Next Top Model

  • 1. Railsā€™ Next Top Model Adam Keys, expert typist at Gowalla http://therealadam.com @therealadam RailsConf 2010 Hi, Iā€™m Adam Keys. Iā€™m an expert typist at Gowalla and an amateur language lawyer. Today Iā€™m going to talk about what I consider the most intriguing part of the reimagination of Rails that is Rails 3. Namely, I want to explore how ActiveRecord was extracted from itself into ActiveModel and ActiveRelation.
  • 2. k s ! L oo ea t G r u r F o * Extractions reduce friction in building little languages on top of data stores * Reduce the boilerplate code involved in bringing up a data layer * Make it easier to add some of the things weā€™ve come to take for granted * Allow developers to focus on building better APIs for data
  • 3. Clean up your domain ActiveSupport fanciness objects * ActiveSupport, the oft-maligned cake on top of ActiveRecord and Rails are built * Smaller and less cumbersome in Rails 3, cherry-pick the functionality you want * Use ActiveSupport instead of rolling your own extensions or copy-paste reuse * Tighten up your classes by extracting concerns
  • 4. require 'common' require 'active_support/inflector' require 'active_support/cache' class User attr_accessor :name def friends cache.fetch("user-#{name}-friends") do %w{ Peter Egon Winston } end end protected def cache ActiveSupport::Cache::MemCacheStore.new end end * Not too different from the user model in your own applications * `cache` is the simplest thing that might work, but could we make it better and cleaner?
  • 5. require 'active_support/core_ext/class' class User cattr_accessor :cache attr_accessor :name def friends cache.fetch("user-#{name}-friends") do %w{ Peter Egon Winston } end end end * Use a class attribute to get the cache conļ¬guration out of the instance * Could use the inheritable version if we are building our own framework
  • 6. User.cache = ActiveSupport::Cache::MemCacheStore.new * In our application setup, create a cache instance and assign it to whatever classes need it
  • 7. def friends cache.fetch("user-#{name}-friends") do %w{ Peter Egon Winston } end end * Suppose weā€™re going to end up with a lot of methods that look like this * Thereā€™s a lot of potential boiler-plate code to write there * Is there a way we can isolate specify a name, key format, and the logic to use?
  • 8. cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end * I like to start by thinking what the little language will look like * From there, I start adding the code to make it go * Hat tip, Rich Kilmer
  • 9. cattr_accessor :cache_lookups, :cache_keys do {} end def self.cache_key(name, key, &block) class_eval %Q{ cache_lookups[name] = block cache_keys[name] = key def #{name} return @#{name} if @#{name}.present? key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do block.call end end } end * Add a couple class attributes to keep track of things, this time with default values * Write a class method that adds a method for each cache key we add * Look up the the cache key to fetch from, look up the body to call to populate it, off we go * The catch: block is bound to class rather than instance
  • 10. class User cattr_accessor :cache attr_accessor :name cattr_accessor :cache_lookups, :cache_keys do {} end def self.cache_key(name, key, &block) class_eval %Q{ cache_lookups[name] = block cache_keys[name] = key def #{name} return @#{name} if @#{name}.present? key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do block.call end end } end cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end end * Downside: now our class wonā€™t ļ¬t nicely on one slide; is this a smell? * ActiveSupport enables a nice little refactoring Iā€™ve started calling ā€œextract concernā€
  • 11. class User cattr_accessor :cache attr_accessor :name cattr_accessor :cache_lookups, :cache_keys do {} end def self.cache_key(name, key, &block) class_eval %Q{ cache_lookups[name] = block cache_keys[name] = key def #{name} return @#{name} if @#{name}.present? key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do block.call end end } end cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end end * Downside: now our class wonā€™t ļ¬t nicely on one slide; is this a smell? * ActiveSupport enables a nice little refactoring Iā€™ve started calling ā€œextract concernā€
  • 12. require 'active_support/concern' module Cacheabilly extend ActiveSupport::Concern included do cattr_accessor :cache cattr_accessor :cache_lookups, :cache_keys do {} end def self.cache_key(name, key, &block) cache_lookups[name] = block cache_keys[name] = key class_eval %Q{ def #{name} return @#{name} if @#{name}.present? key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do block.call end end } end end end * We pick up all the machinery involved in making `cache_key` work and move into a module * Then we wrap that bit in the included hook and extend ActiveSupport::Concern * Easier to read than the old convention of modules included in, ala plugins
  • 13. class User include Cacheabilly attr_accessor :name cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end end * Domain object ļ¬ts on one slide again * Easy to see where the cache behavior comes from
  • 14. Accessors + concerns = slimming effect * ActiveSupport can help remove tedious code from your logic * ActiveSupport can make your classes simpler to reason about * Also look out for handy helper classes like MessageVeriļ¬er/Encryper, SecureRandom, etc. * Give it a fresh look, even if itā€™s previously stabbed you in the face
  • 15. Models that look good ActiveModel validations and want to talk good too * ActiveModel is the result of extracting much of the goodness of ActiveRecord * If youā€™ve ever wanted validations, callbacks, dirty tracking, or serialization, this is your jam * Better still, ActiveModel is cherry-pickable like ActiveSupport
  • 16. include ActiveModel::Validations validates_presence_of :name validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' * Adding validations to our user model is easy * These are one in the same with what youā€™re using in AR * No methods needed to get this functionality; just include and youā€™re on your way
  • 17. class GhostbusterValidator < ActiveModel::Validator def validate(record) names = %w{ Peter Ray Egon Winston } return if names.include?(record.name) record.errors[:base] << "Not a Ghostbuster :(" end end * With ActiveModel, we can also implement validation logic in external classes * Nice for sharing between projects or extracting involved validation
  • 18. validates_with GhostbusterValidator * Step 1: specify your validation class * There is no step 2
  • 19. class User include Cacheabilly attr_accessor :name cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end include ActiveModel::Validations validates_presence_of :name validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' validates_with GhostbusterValidator end * Now our class looks like this * Still ļ¬ts on one slide
  • 20. class User include Cacheabilly attr_accessor :name cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end include ActiveModel::Validations validates_presence_of :name validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' validates_with GhostbusterValidator end * Now our class looks like this * Still ļ¬ts on one slide
  • 21. >> u = User.new => #<User:0x103a56f28> >> u.valid? => false >> u.errors => #<OrderedHash {:base=>["Not a Ghostbuster :("], :name=>["can't be blank", "can't be blank", "Names with less than 3 characters are dumb", "can't be blank", "Names with less than 3 characters are dumb"]}> Using the validations, no surprise, looks just like AR
  • 22. >> u.name = 'Ron' => "Ron" >> u.valid? => false >> u.errors => #<OrderedHash {:base=>["Not a Ghostbuster :("]}> Ron Evans is a cool dude, but heā€™s no Ghostbuster
  • 23. >> u.name = 'Ray' => "Ray" >> u.valid? => true Ray is a bonaļ¬de Ghostbuster
  • 24. Serialize your objects, ActiveModel lifecycle helpers your way * Validations are cool and easy * What if we want to encode our object as JSON or XML * Tyra is not so sure she wants to write that code herself
  • 25. attr_accessor :degree, :thought Letā€™s add a couple more attributes to our class, for grins.
  • 26. def attributes @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought} end def attributes=(hash) self.name = hash['name'] self.degree = hash['degree'] self.thought = hash['thought'] end * If we add `attributes`, AMo knows what attributes to serialize when it encodes your object * If we implement `attributes=`, we can specify how a serialized object gets decoded
  • 27. include ActiveModel::Serializers::JSON include ActiveModel::Serializers::Xml Once thatā€™s done, we pull in the serializers we want to make available.
  • 28. >> u.serializable_hash => {"name"=>"Ray Stanz", "degree"=>"Parapsychology", "thought"=>"The Stay-Puft Marshmallow Man"} >> u.to_json => "{"name":"Ray Stanz","degree":"Parapsychology","thought ":"The Stay-Puft Marshmallow Man"}" >> u.to_xml => "<?xml version="1.0" encoding="UTF-8"?>n<user>n <degree>Parapsychology</degree>n <name>Ray Stanz</name>n <thought>The Stay-Puft Marshmallow Man</thought>n</user>n" * We get a serializable hash method which is what gets encoded * `to_json` and `to_xml` are now ours, just like with AR
  • 29. >> json = u.to_json => "{"name":"Ray Stanz","degree":"Parapsychology","thought ":"The Stay-Puft Marshmallow Man"}" >> new_user = User.new => #<User:0x103166378> >> new_user.from_json(json) => #<User:0x103166378 @name="Ray Stanz", @degree="Parapsychology", @thought="The Stay-Puft Marshmallow Man"> We can even use `from_json` and `from_xml`!
  • 30. Persistence and queries, ActiveRelation persistence like a boss * In Rails 2, AR contains a bunch of logic for banging string together to form queries * In Rails 3, thatā€™s been abstracted into a library that models the relational algebra that databases use * But, Arel makes it possible to use the same API to query all your data sources
  • 31. include Arel::Relation cattr_accessor :engine * To make our class query and persist like an AR object, we need to include `Relation` * Weā€™ll also need an engine, which weā€™ll look into soonly * Including Relation gives us a whole bunch of methods that look familiar from AR: where, order, take, skip, and some that are more SQLlish: insert, update, delete
  • 32. def save insert(self) end def find(name) key = name.downcase.gsub(' ', '_') where("user-#{key}").call end * Since our goal is to be somewhat like AR, weā€™ll add some sugar on top of the Relation * Our save isnā€™t as clever as ARā€™s, in that it only creates records, but we could add dirty tracking later * Find is just sugar on top of `where`, which is quite similar to how we use it in AR
  • 33. def marshal_dump attributes end def marshal_load(hash) self.attributes = hash end * For those following along at home, weā€™ll need these on User too, due to some oddness between AMoā€™s serialization and Arelā€™s each method * Ideally, weā€™d serialize with JSON instead, but this gets the job done for now
  • 34. class UserEngine attr_reader :cache def initialize(cache) @cache = cache end end * Arel implements the query mechanism, but you still need to write an ā€œengineā€ to handle translating to the right query language and reading/writing * These seem to be called engines by convention, but they are basically just a duck type * The methods weā€™ll need to implement are our good CRUD friends
  • 35. def create(insert) record = insert.relation key = record.cache_key value = record cache.write(key, value) end * Getting these engines up is mostly a matter of grokking what is in an ARel relation * Everything is passed a relation * The insert object has a relation that represents the record we want to create * Uses Marshal, JSON or YAML would be nicer
  • 36. def read(select) raise ArgumentError.new("#{select.class} not supported") unless select.is_a?(Arel::Where) key = select.predicates.first.value cache.read(key) end * Reads are where we query the datastore * The select object contains the last method in whatever query chain we called * Our memcached-based gizmo only supports `where` but we could get a `project`, `take`, `order` etc. * Spend lots of time poking the insides of these various objects to grab the data you need to construct a query
  • 37. def update(update) record = update.assignments.value key = record.cache_key value = record cache.write(key, value) end * Update objects contain an assignment, which has the record weā€™re after * Again, uses Marshal, which is suboptimal
  • 38. def delete(delete) key = delete.relation.cache_key cache.delete(key) end * Delete passes the relation weā€™re going to remove; not much going on here
  • 39. class UserEngine attr_reader :cache def initialize(cache) @cache = cache end def create(insert) record = insert.relation key = record.cache_key value = record cache.write(key, value) end def read(select) raise ArgumentError.new("#{select.class} not supported") unless select.is_a? (Arel::Where) key = select.predicates.first.value cache.read(key) end def update(update) record = relation.assignments.value key = record.cache_key value = record cache.write(key, value) end def delete(delete) key = delete.relation.cache_key cache.delete(key) end end * The entirety of our engine * Use this as a starting point for your datastore; itā€™s probably not entirely right for what you want to do, but itā€™s better than trying to ļ¬gure things out from scratch * Read the in-memory engine that comes with ARel or look at arel-mongo
  • 40. User.cache = ActiveSupport::Cache::MemCacheStore.new User.engine = UserEngine.new(User.cache) Hereā€™s how we set up our engine
  • 41. # >> u = User.new # => #<User:0x103655eb0> # >> u.name = 'Ray Stanz' # => "Ray Stanz" # >> u.degree = 'Parapsychology' # => "Parapsychology" # >> u.thought = 'The Stay-Puft Marshmallow Man' # => "The Stay-Puft Marshmallow Man" # >> u.save # => true # >> other = User.new # => #<User:0x103643b20> # >> user.find('Ray Stanz') # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology", @thought="The Stay-Puft Marshmallow Man"> # >> user.thought = '' # => "" # >> user.update(user) # => true # >> user.delete # => true * Create a user object * Save it to the cache * Read it back out * Update it * Delete it
  • 42. # >> u = User.new # => #<User:0x103655eb0> # >> u.name = 'Ray Stanz' # => "Ray Stanz" # >> u.degree = 'Parapsychology' # => "Parapsychology" # >> u.thought = 'The Stay-Puft Marshmallow Man' # => "The Stay-Puft Marshmallow Man" # >> u.save # => true # >> other = User.new # => #<User:0x103643b20> # >> user.find('Ray Stanz') # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology", @thought="The Stay-Puft Marshmallow Man"> # >> user.thought = '' # => "" # >> user.update(user) # => true # >> user.delete # => true * Create a user object * Save it to the cache * Read it back out * Update it * Delete it
  • 43. # >> u = User.new # => #<User:0x103655eb0> # >> u.name = 'Ray Stanz' # => "Ray Stanz" # >> u.degree = 'Parapsychology' # => "Parapsychology" # >> u.thought = 'The Stay-Puft Marshmallow Man' # => "The Stay-Puft Marshmallow Man" # >> u.save # => true # >> other = User.new # => #<User:0x103643b20> # >> user.find('Ray Stanz') # => #<User:0x10363f958 @name="Ray Stanz", @degree="Parapsychology", @thought="The Stay-Puft Marshmallow Man"> # >> user.thought = '' # => "" # >> user.update(user) # => true # >> user.delete # => true * Create a user object * Save it to the cache * Read it back out * Update it * Delete it
  • 44. ~15 collars, BTW * One dude, one pair of shades, one ļ¬‚eshbeard, ļ¬fteen collars * Weā€™ve popped a lot of collars, but we got a lot of functionality too
  • 45. class User include Cacheabilly attr_accessor :name cache_key(:friends, :friends_key) do %w{ Peter Egon Winston } end def friends_key "user-#{name}-friends" end include ActiveModel::Validations validates_presence_of :name validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' validates_with GhostbusterValidator include ActiveModel::Serialization attr_accessor :degree, :thought def attributes @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought} end def attributes=(hash) self.name = hash['name'] self.degree = hash['degree'] self.thought = hash['thought'] end include ActiveModel::Serializers::JSON include ActiveModel::Serializers::Xml include Arel::Relation cattr_accessor :engine # Our engine uses this method to infer the record's key def cache_key "user-#{name.downcase.gsub(' ', '_')}" end def marshal_dump attributes end def marshal_load(hash) self.attributes = hash end def save # HAX: use dirty tracking to call insert or update here insert(self) end def find(name) key = name.downcase.gsub(' ', '_') where("user-#{key}").call end end * Hereā€™s our domain model and hereā€™s all the support code * What we get: declarative lazy caching, validations, serialization, persistence, querying
  • 46. require 'common' require 'active_support/concern' require 'active_support/core_ext/class' require 'active_support/inflector' require 'active_support/cache' require 'active_model' require 'arel' module Cacheabilly extend ActiveSupport::Concern included do cattr_accessor :cache class User include Cacheabilly cattr_accessor :cache_lookups, :cache_keys do {} attr_accessor :name end cache_key(:friends, :friends_key) do def self.cache_key(name, key, &block) %w{ Peter Egon Winston } cache_lookups[name] = block end cache_keys[name] = key def friends_key class_eval %Q{ "user-#{name}-friends" end def #{name} return @#{name} if @#{name}.present? include ActiveModel::Validations key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do validates_presence_of :name block.call validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' end validates_with GhostbusterValidator end } include ActiveModel::Serialization end end attr_accessor :degree, :thought end def attributes class GhostbusterValidator < ActiveModel::Validator @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought} end def validate(record) return if %w{ Peter Ray Egon Winston }.include?(record.name) def attributes=(hash) record.errors[:base] << "Not a Ghostbuster :(" self.name = hash['name'] end self.degree = hash['degree'] self.thought = hash['thought'] end end include ActiveModel::Serializers::JSON include ActiveModel::Serializers::Xml include Arel::Relation cattr_accessor :engine # Our engine uses this method to infer the record's key def cache_key "user-#{name.downcase.gsub(' ', '_')}" end def marshal_dump attributes end def marshal_load(hash) self.attributes = hash end def save # HAX: use dirty tracking to call insert or update here insert(self) end def find(name) key = name.downcase.gsub(' ', '_') where("user-#{key}").call end end * Hereā€™s our domain model and hereā€™s all the support code * What we get: declarative lazy caching, validations, serialization, persistence, querying
  • 47. require 'common' require 'active_support/concern' require 'active_support/core_ext/class' require 'active_support/inflector' require 'active_support/cache' require 'active_model' require 'arel' module Cacheabilly extend ActiveSupport::Concern included do cattr_accessor :cache class User include Cacheabilly cattr_accessor :cache_lookups, :cache_keys do {} attr_accessor :name end cache_key(:friends, :friends_key) do def self.cache_key(name, key, &block) %w{ Peter Egon Winston } cache_lookups[name] = block end cache_keys[name] = key def friends_key class_eval %Q{ "user-#{name}-friends" end def #{name} return @#{name} if @#{name}.present? include ActiveModel::Validations key = method(cache_keys[:#{name}]).call @#{name} = cache.fetch(key) do validates_presence_of :name block.call validates_length_of :name, :minimum => 3, :message => 'Names with less than 3 characters are dumb' end validates_with GhostbusterValidator end } include ActiveModel::Serialization end end attr_accessor :degree, :thought end def attributes class GhostbusterValidator < ActiveModel::Validator @attributes ||= {'name' => name, 'degree' => degree, 'thought' => thought} end def validate(record) return if %w{ Peter Ray Egon Winston }.include?(record.name) def attributes=(hash) record.errors[:base] << "Not a Ghostbuster :(" self.name = hash['name'] end self.degree = hash['degree'] self.thought = hash['thought'] end end class UserEngine include ActiveModel::Serializers::JSON include ActiveModel::Serializers::Xml attr_reader :cache include Arel::Relation def initialize(cache) @cache = cache cattr_accessor :engine end # Our engine uses this method to infer the record's key def create(insert) def cache_key record = insert.relation "user-#{name.downcase.gsub(' ', '_')}" key = record.cache_key end value = record # Note: this uses Marshal, b/c to_json w/ arel is buggy cache.write(key, value) def marshal_dump end attributes end # Ignores chained queries, i.e. take(n).where(...) def read(select) def marshal_load(hash) raise ArgumentError.new("#{select.class} not supported") unless select.is_a?(Arel::Where) self.attributes = hash key = select.predicates.first.value end cache.read(key) end def save # HAX: use dirty tracking to call insert or update here def update(update) insert(self) record = relation.assignments.value end key = record.cache_key value = record def find(name) cache.write(key, value) key = name.downcase.gsub(' ', '_') end where("user-#{key}").call end def delete(delete) key = delete.relation.cache_key end cache.delete(key) end end User.cache = ActiveSupport::Cache::MemCacheStore.new('localhost') User.engine = UserEngine.new(User.cache) * Hereā€™s our domain model and hereā€™s all the support code * What we get: declarative lazy caching, validations, serialization, persistence, querying
  • 48. ā€œKeysā€ to success ą¹ give ActiveSupport a try ą¹ fancy up your classes with AMo ą¹ build data layers with ARel ą¹ make better codes
  • 49. Thanks! I hope you all got something out of this talk. Iā€™ll post the slides and example code on my website, therealadam.com, soon. Thanks for coming!