SlideShare uma empresa Scribd logo
1 de 49
Baixar para ler 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 configuration 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 fit 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 fit 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 fits 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 MessageVerifier/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 fits 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 fits 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 bonafide 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 figure 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 fleshbeard, fifteen 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!

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Destaque

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
 
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
 
統計的因果推論勉強会 第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
 

Destaque (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
 

Semelhante a 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
 
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
 
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
 

Semelhante a 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
 

Último

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024The Digital Insurer
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
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
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
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
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
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
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 

Último (20)

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
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
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
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
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 

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 configuration 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 fit 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 fit 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 fits 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 MessageVerifier/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 fits 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 fits 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 bonafide 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 figure 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 fleshbeard, fifteen 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!