ambry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog.md +5 -0
 - data/Gemfile +2 -0
 - data/Guide.md +320 -0
 - data/MIT-LICENSE +18 -0
 - data/README.md +97 -0
 - data/Rakefile +39 -0
 - data/ambry.gemspec +25 -0
 - data/extras/bench.rb +107 -0
 - data/extras/cookie_demo.rb +111 -0
 - data/extras/countries.rb +70 -0
 - data/lib/ambry.rb +54 -0
 - data/lib/ambry/abstract_key_set.rb +106 -0
 - data/lib/ambry/active_model.rb +122 -0
 - data/lib/ambry/adapter.rb +53 -0
 - data/lib/ambry/adapters/cookie.rb +55 -0
 - data/lib/ambry/adapters/file.rb +38 -0
 - data/lib/ambry/adapters/yaml.rb +17 -0
 - data/lib/ambry/hash_proxy.rb +55 -0
 - data/lib/ambry/mapper.rb +66 -0
 - data/lib/ambry/model.rb +164 -0
 - data/lib/ambry/version.rb +9 -0
 - data/lib/generators/norman_generator.rb +22 -0
 - data/lib/rack/norman.rb +21 -0
 - data/spec/active_model_spec.rb +115 -0
 - data/spec/adapter_spec.rb +48 -0
 - data/spec/cookie_adapter_spec.rb +81 -0
 - data/spec/file_adapter_spec.rb +48 -0
 - data/spec/fixtures.yml +18 -0
 - data/spec/key_set_spec.rb +104 -0
 - data/spec/mapper_spec.rb +97 -0
 - data/spec/model_spec.rb +162 -0
 - data/spec/spec_helper.rb +38 -0
 - metadata +147 -0
 
| 
         @@ -0,0 +1,122 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "active_model"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Extend this module if you want {Active Model}[http://github.com/rails/rails/tree/master/activemodel]
         
     | 
| 
      
 5 
     | 
    
         
            +
              # support. Active Model is an API provided by Rails to make any Ruby object
         
     | 
| 
      
 6 
     | 
    
         
            +
              # behave like an Active Record model instance. You can read an older writeup
         
     | 
| 
      
 7 
     | 
    
         
            +
              # about it {here}[http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/].
         
     | 
| 
      
 8 
     | 
    
         
            +
              module ActiveModel
         
     | 
| 
      
 9 
     | 
    
         
            +
                def self.extended(base)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  base.instance_eval do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    extend  ClassMethods
         
     | 
| 
      
 12 
     | 
    
         
            +
                    include InstanceMethods
         
     | 
| 
      
 13 
     | 
    
         
            +
                    extend  ::ActiveModel::Naming
         
     | 
| 
      
 14 
     | 
    
         
            +
                    extend  ::ActiveModel::Translation
         
     | 
| 
      
 15 
     | 
    
         
            +
                    include ::ActiveModel::Validations
         
     | 
| 
      
 16 
     | 
    
         
            +
                    include ::ActiveModel::Serializers::JSON
         
     | 
| 
      
 17 
     | 
    
         
            +
                    include ::ActiveModel::Serializers::Xml
         
     | 
| 
      
 18 
     | 
    
         
            +
                    extend  ::ActiveModel::Callbacks
         
     | 
| 
      
 19 
     | 
    
         
            +
                    define_model_callbacks :save, :destroy
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # Custom validations.
         
     | 
| 
      
 24 
     | 
    
         
            +
                module Validations
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # A uniqueness validator, similar to the one provided by Active Record.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  class Uniqueness< ::ActiveModel::EachValidator
         
     | 
| 
      
 27 
     | 
    
         
            +
                    def validate_each(record, attribute, value)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      return if record.persisted?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      if attribute.to_sym == record.class.id_method
         
     | 
| 
      
 30 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 31 
     | 
    
         
            +
                          if record.class.mapper[value]
         
     | 
| 
      
 32 
     | 
    
         
            +
                            record.errors[attribute] << "must be unique"
         
     | 
| 
      
 33 
     | 
    
         
            +
                          end
         
     | 
| 
      
 34 
     | 
    
         
            +
                        rescue Ambry::NotFoundError
         
     | 
| 
      
 35 
     | 
    
         
            +
                        end
         
     | 
| 
      
 36 
     | 
    
         
            +
                      else
         
     | 
| 
      
 37 
     | 
    
         
            +
                        if record.class.all.detect {|x| x.send(attribute) == value}
         
     | 
| 
      
 38 
     | 
    
         
            +
                          record.errors[attribute] << "must be unique"
         
     | 
| 
      
 39 
     | 
    
         
            +
                        end
         
     | 
| 
      
 40 
     | 
    
         
            +
                      end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # Create and save a model instance, raising an exception if any errors
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # occur.
         
     | 
| 
      
 48 
     | 
    
         
            +
                  def create!(*args)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    new(*args).save!
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  # Validate the uniqueness of a field's value in a model instance.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def validates_uniqueness_of(*attr_names)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    validates_with Validations::Uniqueness, _merge_attributes(attr_names)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def model_name
         
     | 
| 
      
 58 
     | 
    
         
            +
                    @model_name ||= ::ActiveModel::Name.new(self)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                module InstanceMethods
         
     | 
| 
      
 63 
     | 
    
         
            +
                  def initialize(*args)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @new_record = true
         
     | 
| 
      
 65 
     | 
    
         
            +
                    super
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def attributes
         
     | 
| 
      
 69 
     | 
    
         
            +
                    hash = to_hash
         
     | 
| 
      
 70 
     | 
    
         
            +
                    hash.keys.each {|k| hash[k.to_s] = hash.delete(k)}
         
     | 
| 
      
 71 
     | 
    
         
            +
                    hash
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  def keys
         
     | 
| 
      
 75 
     | 
    
         
            +
                    self.class.attribute_names
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  def to_model
         
     | 
| 
      
 79 
     | 
    
         
            +
                    self
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  def new_record?
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @new_record
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  def persisted?
         
     | 
| 
      
 87 
     | 
    
         
            +
                    !new_record?
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  def save
         
     | 
| 
      
 91 
     | 
    
         
            +
                    run_callbacks(:save) do
         
     | 
| 
      
 92 
     | 
    
         
            +
                      @new_record = false
         
     | 
| 
      
 93 
     | 
    
         
            +
                      super
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  def save!
         
     | 
| 
      
 98 
     | 
    
         
            +
                    if !valid?
         
     | 
| 
      
 99 
     | 
    
         
            +
                      raise Ambry::AmbryError, errors.to_a.join(", ")
         
     | 
| 
      
 100 
     | 
    
         
            +
                    else
         
     | 
| 
      
 101 
     | 
    
         
            +
                      save
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                  def to_param
         
     | 
| 
      
 106 
     | 
    
         
            +
                    to_id if persisted?
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  def to_key
         
     | 
| 
      
 110 
     | 
    
         
            +
                    [to_param] if persisted?
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  def destroy
         
     | 
| 
      
 114 
     | 
    
         
            +
                    run_callbacks(:destroy) { delete }
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  def update_attributes
         
     | 
| 
      
 118 
     | 
    
         
            +
                    run_callbacks(:save) { update }
         
     | 
| 
      
 119 
     | 
    
         
            +
                  end
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # Adapters are responsible for persisting the database. This base adapter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # offers no persistence, all IO operations are just stubs. Adapters must also
         
     | 
| 
      
 5 
     | 
    
         
            +
              # present the full database as a Hash to the mapper, and provide a `key`
         
     | 
| 
      
 6 
     | 
    
         
            +
              # method that returns an  array with all the keys for the specified model
         
     | 
| 
      
 7 
     | 
    
         
            +
              # class.
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Adapter
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :name
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :db
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # @option options [String] :name The adapter name. Defaults to {#Ambry.default_adapter_name}.
         
     | 
| 
      
 14 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @name = options[:name] || Ambry.default_adapter_name
         
     | 
| 
      
 16 
     | 
    
         
            +
                  load_database
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Ambry.register_adapter(self)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                # Get a hash of all the data for the specified model class.
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @param klass [#to_s] The model class whose data to return.
         
     | 
| 
      
 22 
     | 
    
         
            +
                def db_for(klass)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @db[klass.to_s] ||= {}
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # Loads the database. For this adapter, that means simply creating a new
         
     | 
| 
      
 27 
     | 
    
         
            +
                # hash.
         
     | 
| 
      
 28 
     | 
    
         
            +
                def load_database
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @db = {}
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # These are all just noops for this adapter, which uses an in-memory hash
         
     | 
| 
      
 33 
     | 
    
         
            +
                # and offers no persistence.
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                # Inheriting adapters can overload this method to export the data to a
         
     | 
| 
      
 36 
     | 
    
         
            +
                # String.
         
     | 
| 
      
 37 
     | 
    
         
            +
                def export_data
         
     | 
| 
      
 38 
     | 
    
         
            +
                  true
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                # Inheriting adapters can overload this method to load the data from some
         
     | 
| 
      
 42 
     | 
    
         
            +
                # kind of storage.
         
     | 
| 
      
 43 
     | 
    
         
            +
                def import_data
         
     | 
| 
      
 44 
     | 
    
         
            +
                  true
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # Inheriting adapters can overload this method to persist the data to some
         
     | 
| 
      
 48 
     | 
    
         
            +
                # kind of storage.
         
     | 
| 
      
 49 
     | 
    
         
            +
                def save_database
         
     | 
| 
      
 50 
     | 
    
         
            +
                  true
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "active_support"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "active_support/message_verifier"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "zlib"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Adapters
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # Ambry's cookie adapter allows you to store a Ambry database inside
         
     | 
| 
      
 9 
     | 
    
         
            +
                # a zipped and signed string suitable for setting as a cookie. This can be
         
     | 
| 
      
 10 
     | 
    
         
            +
                # useful for modelling things like basic shopping carts or form wizards.
         
     | 
| 
      
 11 
     | 
    
         
            +
                # Keep in mind the data is signed, so it can't be tampered with. However,
         
     | 
| 
      
 12 
     | 
    
         
            +
                # the data is not *encrypted*, so somebody that wanted to could unzip and
         
     | 
| 
      
 13 
     | 
    
         
            +
                # load the cookie data to see what's inside. So don't send this data
         
     | 
| 
      
 14 
     | 
    
         
            +
                # client-side if it's at all sensitive.
         
     | 
| 
      
 15 
     | 
    
         
            +
                class Cookie < Adapter
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  attr :verifier
         
     | 
| 
      
 18 
     | 
    
         
            +
                  attr_accessor :data
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  MAX_DATA_LENGTH = 4096
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def self.max_data_length
         
     | 
| 
      
 23 
     | 
    
         
            +
                    MAX_DATA_LENGTH
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def initialize(options)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @data     = options[:data]
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @verifier = ActiveSupport::MessageVerifier.new(options[:secret])
         
     | 
| 
      
 29 
     | 
    
         
            +
                    super
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def export_data
         
     | 
| 
      
 33 
     | 
    
         
            +
                    cookie = verifier.generate(Zlib::Deflate.deflate(Marshal.dump(db)))
         
     | 
| 
      
 34 
     | 
    
         
            +
                    length = cookie.bytesize
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if length > Cookie.max_data_length
         
     | 
| 
      
 36 
     | 
    
         
            +
                      raise(AmbryError, "Data is %s bytes, cannot exceed %s" % [length, Cookie.max_data_length])
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                    cookie
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def import_data
         
     | 
| 
      
 42 
     | 
    
         
            +
                    data.blank? ? {} : Marshal.load(Zlib::Inflate.inflate(verifier.verify(data)))
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def load_database
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @db = import_data
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @db.map(&:freeze)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def save_database
         
     | 
| 
      
 51 
     | 
    
         
            +
                    @data = export_data
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Adapters
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Loads and saves hash database from a Marshal.dump file.
         
     | 
| 
      
 4 
     | 
    
         
            +
                class File < Adapter
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :file_path
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr :lock
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(options)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @file_path = options[:file]
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @lock      = Mutex.new
         
     | 
| 
      
 12 
     | 
    
         
            +
                    super
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def load_database
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @db = import_data
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @db.blank? ? @db = {} : @db.map(&:freeze)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  rescue Errno::ENOENT
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # @TODO warn via logger when file doesn't exist
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @db = {}
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def export_data
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Marshal.dump(db)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def import_data
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Marshal.load(::File.read(file_path))
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def save_database
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @lock.synchronize do
         
     | 
| 
      
 33 
     | 
    
         
            +
                      ::File.open(file_path, "w") {|f| f.write(export_data)}
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Adapters
         
     | 
| 
      
 5 
     | 
    
         
            +
                # An Adapter that uses YAML for its storage.
         
     | 
| 
      
 6 
     | 
    
         
            +
                class YAML < File
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def import_data
         
     | 
| 
      
 9 
     | 
    
         
            +
                    data = ::YAML.load(::File.read(file_path))
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def export_data
         
     | 
| 
      
 13 
     | 
    
         
            +
                    db.to_yaml
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Wrapper around hash instances that allows values to be accessed as symbols,
         
     | 
| 
      
 3 
     | 
    
         
            +
              # strings or method invocations. It behaves similary to OpenStruct, with the
         
     | 
| 
      
 4 
     | 
    
         
            +
              # fundamental difference being that you instantiate *one* HashProxy instance
         
     | 
| 
      
 5 
     | 
    
         
            +
              # and reassign its Hash during a loop in order to avoid creating garbage.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class HashProxy
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr :hash
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                # Allows accessing a hash attribute as a method.
         
     | 
| 
      
 10 
     | 
    
         
            +
                def method_missing(symbol)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  hash[symbol] or raise NoMethodError
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # Allows accessing a hash attribute as hash key, either a string or symbol.
         
     | 
| 
      
 15 
     | 
    
         
            +
                def [](value)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  hash[value || value.to_sym || value.to_s]
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Remove the hash.
         
     | 
| 
      
 20 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @hash = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # Assign the value to hash and return self.
         
     | 
| 
      
 25 
     | 
    
         
            +
                def using(hash)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @hash = hash ; self
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # Set the hash to use while calling the block. When the block ends, the
         
     | 
| 
      
 30 
     | 
    
         
            +
                # hash is unset.
         
     | 
| 
      
 31 
     | 
    
         
            +
                def with(hash, &block)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  yield using hash ensure clear
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              # Like HashProxy, but proxies access to two or more Hash instances.
         
     | 
| 
      
 37 
     | 
    
         
            +
              class HashProxySet
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                attr :proxies
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @proxies = []
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def using(*args)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  args.size.times { proxies.push HashProxy.new } if proxies.empty?
         
     | 
| 
      
 47 
     | 
    
         
            +
                  proxies.each_with_index {|proxy, index| proxy.using args[index] }
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 51 
     | 
    
         
            +
                  proxies.map(&:clear)
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ambry/mapper.rb
    ADDED
    
    | 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # Mappers provide the middle ground between models and adapters. Mappers are
         
     | 
| 
      
 4 
     | 
    
         
            +
              # responsible for performing finds and moving objects in and out of the
         
     | 
| 
      
 5 
     | 
    
         
            +
              # hash.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Mapper
         
     | 
| 
      
 7 
     | 
    
         
            +
                extend Forwardable
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr :hash
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :adapter_name, :klass, :indexes, :options
         
     | 
| 
      
 10 
     | 
    
         
            +
                def_delegators :hash, :clear, :delete
         
     | 
| 
      
 11 
     | 
    
         
            +
                def_delegators :key_set, :all, :count, :find, :find_by_key, :first, :keys
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(klass, adapter_name = nil, options = {})
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @klass        = klass
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @adapter_name = adapter_name || Ambry.default_adapter_name
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @indexes      = {}
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @lock         = Mutex.new
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @options      = options
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @hash         = adapter.db_for(klass)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # Returns a hash or model attributes corresponding to the provided key.
         
     | 
| 
      
 23 
     | 
    
         
            +
                def [](key)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  hash[key] or raise NotFoundError.new(klass, key)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                # Sets a hash by key.
         
     | 
| 
      
 28 
     | 
    
         
            +
                def []=(key, value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @lock.synchronize do
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @indexes = {}
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if value.id_changed?
         
     | 
| 
      
 32 
     | 
    
         
            +
                      hash.delete value.to_id(true)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    saved = hash[key] = value.to_hash.freeze
         
     | 
| 
      
 35 
     | 
    
         
            +
                    adapter.save_database if @options[:sync]
         
     | 
| 
      
 36 
     | 
    
         
            +
                    saved
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                # Memoize the output of a find in a threadsafe manner.
         
     | 
| 
      
 41 
     | 
    
         
            +
                def add_index(name, indexable)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @lock.synchronize do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @indexes[name] = indexable
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # Get the adapter.
         
     | 
| 
      
 48 
     | 
    
         
            +
                def adapter
         
     | 
| 
      
 49 
     | 
    
         
            +
                  Ambry.adapters[adapter_name]
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                # Get an instance by key
         
     | 
| 
      
 53 
     | 
    
         
            +
                def get(key)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  klass.send :from_hash, self[key]
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def key_set
         
     | 
| 
      
 58 
     | 
    
         
            +
                  klass.key_class.new(hash.keys.freeze, self)
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                # Sets an instance, invoking its to_id method
         
     | 
| 
      
 62 
     | 
    
         
            +
                def put(instance)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  self[instance.to_id] = instance
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ambry/model.rb
    ADDED
    
    | 
         @@ -0,0 +1,164 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Ambry
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 4 
     | 
    
         
            +
                def self.extended(base)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  base.instance_eval do
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @lock            = Mutex.new
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @attribute_names = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @key_class       = Class.new(Ambry::AbstractKeySet)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extend  ClassMethods
         
     | 
| 
      
 10 
     | 
    
         
            +
                    include InstanceMethods
         
     | 
| 
      
 11 
     | 
    
         
            +
                    include Comparable
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 16 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 17 
     | 
    
         
            +
                  attr_accessor :attribute_names, :id_method, :mapper
         
     | 
| 
      
 18 
     | 
    
         
            +
                  attr_reader :key_class
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def_delegators(*[:find, Enumerable.public_instance_methods(false)].flatten)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def_delegators(:mapper, :[], :all, :delete, :first, :get, :count, :find, :find_by_key, :keys)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  alias id_field id_method=
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def field(*names)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    names.each do |name|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      # First attribute added is the default id
         
     | 
| 
      
 26 
     | 
    
         
            +
                      id_field name if attribute_names.empty?
         
     | 
| 
      
 27 
     | 
    
         
            +
                      attribute_names << name.to_sym
         
     | 
| 
      
 28 
     | 
    
         
            +
                      class_eval(<<-EOM, __FILE__, __LINE__ + 1)
         
     | 
| 
      
 29 
     | 
    
         
            +
                        def #{name}
         
     | 
| 
      
 30 
     | 
    
         
            +
                          @#{name} or (@attributes[:#{name}] if @attributes)
         
     | 
| 
      
 31 
     | 
    
         
            +
                        end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                        def #{name}=(value)
         
     | 
| 
      
 34 
     | 
    
         
            +
                          @#{name} = value
         
     | 
| 
      
 35 
     | 
    
         
            +
                        end
         
     | 
| 
      
 36 
     | 
    
         
            +
                      EOM
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def use(adapter_name, options = {})
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @mapper         = nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @adapter_name   = adapter_name
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @mapper_options = options
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  # Memoize the output of the method call invoked in the block.
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # @param [#to_s] name If not given, the name of the method calling with_index will be used.
         
     | 
| 
      
 48 
     | 
    
         
            +
                  def with_index(name = nil, &block)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    name ||= caller(1)[0].match(/in `(.*)'\z/)[1]
         
     | 
| 
      
 50 
     | 
    
         
            +
                    mapper.indexes[name.to_s] or begin
         
     | 
| 
      
 51 
     | 
    
         
            +
                      indexable = yield
         
     | 
| 
      
 52 
     | 
    
         
            +
                      mapper.add_index(name, indexable)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def create(hash)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    new(hash).save
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  # The point of this method is to provide a fast way to get model instances
         
     | 
| 
      
 61 
     | 
    
         
            +
                  # based on the hash attributes managed by the mapper and adapter.
         
     | 
| 
      
 62 
     | 
    
         
            +
                  #
         
     | 
| 
      
 63 
     | 
    
         
            +
                  # The hash arg gets frozen, which can be a nasty side-effect, but helps
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # avoid hard-to-track-down bugs if the hash is updated somewhere outside
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # the model. This should only be used internally to Ambry, which is why
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # it's private.
         
     | 
| 
      
 67 
     | 
    
         
            +
                  def from_hash(hash)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    instance = allocate
         
     | 
| 
      
 69 
     | 
    
         
            +
                    instance.instance_variable_set :@attributes, hash.freeze
         
     | 
| 
      
 70 
     | 
    
         
            +
                    instance
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  private :from_hash
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  def filters(&block)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    key_class.class_eval(&block)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    key_class.instance_methods(false).each do |name|
         
     | 
| 
      
 77 
     | 
    
         
            +
                    instance_eval(<<-EOM, __FILE__, __LINE__ + 1)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      def #{name}(*args)
         
     | 
| 
      
 79 
     | 
    
         
            +
                        mapper.key_set.#{name}(*args)
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
      
 81 
     | 
    
         
            +
                    EOM
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def mapper
         
     | 
| 
      
 86 
     | 
    
         
            +
                    @mapper or @lock.synchronize do
         
     | 
| 
      
 87 
     | 
    
         
            +
                      name      = @adapter_name || Ambry.default_adapter_name
         
     | 
| 
      
 88 
     | 
    
         
            +
                      options   = @mapper_options || {}
         
     | 
| 
      
 89 
     | 
    
         
            +
                      @mapper ||= Mapper.new(self, name, options)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                module InstanceMethods
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  # Ambry models can be instantiated with a hash of attribures, a block,
         
     | 
| 
      
 97 
     | 
    
         
            +
                  # or both. If both a hash and block are given, then the values set inside
         
     | 
| 
      
 98 
     | 
    
         
            +
                  # the block will take precedence over those set in the hash.
         
     | 
| 
      
 99 
     | 
    
         
            +
                  #
         
     | 
| 
      
 100 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 101 
     | 
    
         
            +
                  #   Person.new :name => "Joe"
         
     | 
| 
      
 102 
     | 
    
         
            +
                  #   Person.new {|p| p.name = "Joe"}
         
     | 
| 
      
 103 
     | 
    
         
            +
                  #   Person.new(params[:person]) {|p| p.age = 38}
         
     | 
| 
      
 104 
     | 
    
         
            +
                  #
         
     | 
| 
      
 105 
     | 
    
         
            +
                  def initialize(attributes = nil, &block)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    @attributes = {}.freeze
         
     | 
| 
      
 107 
     | 
    
         
            +
                    return unless attributes || block_given?
         
     | 
| 
      
 108 
     | 
    
         
            +
                    if attributes
         
     | 
| 
      
 109 
     | 
    
         
            +
                      self.class.attribute_names.each do |name|
         
     | 
| 
      
 110 
     | 
    
         
            +
                        value = attributes[name] || attributes[name.to_s]
         
     | 
| 
      
 111 
     | 
    
         
            +
                        send("#{name}=", value) if value
         
     | 
| 
      
 112 
     | 
    
         
            +
                      end
         
     | 
| 
      
 113 
     | 
    
         
            +
                    end
         
     | 
| 
      
 114 
     | 
    
         
            +
                    yield self if block_given?
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  # Ambry models implement the <=> method and mix in Comparable to provide
         
     | 
| 
      
 118 
     | 
    
         
            +
                  # sorting methods. This default implementation compares the result of
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # #to_id. If the items being compared are not of the same kind.
         
     | 
| 
      
 120 
     | 
    
         
            +
                  def <=>(instance)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    to_id <=> instance.to_id if instance.kind_of? self.class
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  # Get a hash of the instance's model attributes.
         
     | 
| 
      
 125 
     | 
    
         
            +
                  def to_hash
         
     | 
| 
      
 126 
     | 
    
         
            +
                    self.class.attribute_names.inject({}) do |hash, key|
         
     | 
| 
      
 127 
     | 
    
         
            +
                      hash[key] = self.send(key); hash
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  # Returns true is the model's id field has been updated.
         
     | 
| 
      
 132 
     | 
    
         
            +
                  def id_changed?
         
     | 
| 
      
 133 
     | 
    
         
            +
                    to_id != @attributes[self.class.id_method]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  # Invoke the model's id method to return this instance's unique key. If
         
     | 
| 
      
 137 
     | 
    
         
            +
                  # true is passed, then the id will be read from the attributes hash rather
         
     | 
| 
      
 138 
     | 
    
         
            +
                  # than from an instance variable. This allows you to retrieve the old id,
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # in the event that the id has been changed.
         
     | 
| 
      
 140 
     | 
    
         
            +
                  def to_id(use_old = false)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    use_old ? @attributes[self.class.id_method] : send(self.class.id_method)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                  # Tell the mapper to save the data for this model instance.
         
     | 
| 
      
 145 
     | 
    
         
            +
                  def save
         
     | 
| 
      
 146 
     | 
    
         
            +
                    self.class.mapper.put(self)
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  # Update this instance's attributes and invoke #save.
         
     | 
| 
      
 150 
     | 
    
         
            +
                  def update(attributes)
         
     | 
| 
      
 151 
     | 
    
         
            +
                    self.class.attribute_names.each do |name|
         
     | 
| 
      
 152 
     | 
    
         
            +
                      value = attributes[name] || attributes[name.to_s]
         
     | 
| 
      
 153 
     | 
    
         
            +
                      send("#{name}=", value) if value
         
     | 
| 
      
 154 
     | 
    
         
            +
                    end
         
     | 
| 
      
 155 
     | 
    
         
            +
                    save
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  # Tell the mapper to delete the data for this instance.
         
     | 
| 
      
 159 
     | 
    
         
            +
                  def delete
         
     | 
| 
      
 160 
     | 
    
         
            +
                    self.class.delete(self.to_id)
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
                end
         
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
            end
         
     |