]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/gems/composite_primary_keys-2.2.2/lib/composite_primary_keys/base.rb
Merge branch 'master' into copyright
[rails.git] / vendor / gems / composite_primary_keys-2.2.2 / lib / composite_primary_keys / base.rb
index a4c7ff93abbf6583e4f228baeb140055ecb6e2d3..4558f97a3dbcd84b7821fa10e962c3fb42ef21e8 100644 (file)
-module CompositePrimaryKeys\r
-  module ActiveRecord #:nodoc:\r
-    class CompositeKeyError < StandardError #:nodoc:\r
-    end\r
-\r
-    module Base #:nodoc:\r
-\r
-      INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'\r
-      NOT_IMPLEMENTED_YET        = 'Not implemented for composite primary keys yet'\r
-\r
-      def self.append_features(base)\r
-        super\r
-        base.send(:include, InstanceMethods)\r
-        base.extend(ClassMethods)\r
-      end\r
-\r
-      module ClassMethods\r
-        def set_primary_keys(*keys)\r
-          keys = keys.first if keys.first.is_a?(Array)\r
-          keys = keys.map { |k| k.to_sym }\r
-          cattr_accessor :primary_keys\r
-          self.primary_keys = keys.to_composite_keys\r
-\r
-          class_eval <<-EOV\r
-            extend CompositeClassMethods\r
-            include CompositeInstanceMethods\r
-\r
-            include CompositePrimaryKeys::ActiveRecord::Associations\r
-            include CompositePrimaryKeys::ActiveRecord::AssociationPreload\r
-            include CompositePrimaryKeys::ActiveRecord::Calculations\r
-            include CompositePrimaryKeys::ActiveRecord::AttributeMethods\r
-          EOV\r
-        end\r
-\r
-        def composite?\r
-          false\r
-        end\r
-      end\r
-\r
-      module InstanceMethods\r
-        def composite?; self.class.composite?; end\r
-      end\r
-\r
-      module CompositeInstanceMethods\r
-\r
-        # A model instance's primary keys is always available as model.ids\r
-        # whether you name it the default 'id' or set it to something else.\r
-        def id\r
-          attr_names = self.class.primary_keys\r
-          CompositeIds.new(attr_names.map { |attr_name| read_attribute(attr_name) })\r
-        end\r
-        alias_method :ids, :id\r
-\r
-        def to_param\r
-          id.to_s\r
-        end\r
-\r
-        def id_before_type_cast #:nodoc:\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET\r
-        end\r
-\r
-        def quoted_id #:nodoc:\r
-          [self.class.primary_keys, ids].\r
-            transpose.\r
-            map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}.\r
-            to_composite_ids\r
-        end\r
-\r
-        # Sets the primary ID.\r
-        def id=(ids)\r
-          ids = ids.split(ID_SEP) if ids.is_a?(String)\r
-          ids.flatten!\r
-          unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length\r
-            raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"\r
-          end\r
-          [primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}\r
-          id\r
-        end\r
-\r
-        # Returns a clone of the record that hasn't been assigned an id yet and\r
-        # is treated as a new record.  Note that this is a "shallow" clone:\r
-        # it copies the object's attributes only, not its associations.\r
-        # The extent of a "deep" clone is application-specific and is therefore\r
-        # left to the application to implement according to its need.\r
-        def clone\r
-          attrs = self.attributes_before_type_cast\r
-          self.class.primary_keys.each {|key| attrs.delete(key.to_s)}\r
-          self.class.new do |record|\r
-            record.send :instance_variable_set, '@attributes', attrs\r
-          end\r
-        end\r
-\r
-\r
-        private\r
-        # The xx_without_callbacks methods are overwritten as that is the end of the alias chain\r
-\r
-        # Creates a new record with values matching those of the instance attributes.\r
-        def create_without_callbacks\r
-          unless self.id\r
-            raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values"\r
-          end\r
-          attributes_minus_pks = attributes_with_quotes(false)\r
-          quoted_pk_columns = self.class.primary_key.map { |col| connection.quote_column_name(col) }\r
-          cols = quoted_column_names(attributes_minus_pks) << quoted_pk_columns\r
-          vals = attributes_minus_pks.values << quoted_id\r
-          connection.insert(\r
-            "INSERT INTO #{self.class.quoted_table_name} " +\r
-            "(#{cols.join(', ')}) " +\r
-            "VALUES (#{vals.join(', ')})",\r
-            "#{self.class.name} Create",\r
-            self.class.primary_key,\r
-            self.id\r
-          )\r
-          @new_record = false\r
-          return true\r
-        end\r
-\r
-        # Updates the associated record with values matching those of the instance attributes.\r
-        def update_without_callbacks\r
-          where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| \r
-            "(#{connection.quote_column_name(pair[0])} = #{pair[1]})"\r
-          end\r
-          where_clause = where_clause_terms.join(" AND ")\r
-          connection.update(\r
-            "UPDATE #{self.class.quoted_table_name} " +\r
-            "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +\r
-            "WHERE #{where_clause}",\r
-            "#{self.class.name} Update"\r
-          )\r
-          return true\r
-        end\r
-\r
-        # Deletes the record in the database and freezes this instance to reflect that no changes should\r
-        # be made (since they can't be persisted).\r
-        def destroy_without_callbacks\r
-          where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| \r
-            "(#{connection.quote_column_name(pair[0])} = #{pair[1]})"\r
-          end\r
-          where_clause = where_clause_terms.join(" AND ")\r
-          unless new_record?\r
-            connection.delete(\r
-              "DELETE FROM #{self.class.quoted_table_name} " +\r
-              "WHERE #{where_clause}",\r
-              "#{self.class.name} Destroy"\r
-            )\r
-          end\r
-          freeze\r
-        end\r
-      end\r
-\r
-      module CompositeClassMethods\r
-        def primary_key; primary_keys; end\r
-        def primary_key=(keys); primary_keys = keys; end\r
-\r
-        def composite?\r
-          true\r
-        end\r
-\r
-        #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"\r
-        #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"\r
-        def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')\r
-          many_ids.map {|ids| "#{left_bracket}#{ids}#{right_bracket}"}.join(list_sep)\r
-        end\r
-        \r
-        # Creates WHERE condition from list of composited ids\r
-        #   User.update_all({:role => 'admin'}, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> UPDATE admins SET admin.role='admin' WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2)\r
-        #   User.find(:all, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> SELECT * FROM admins WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2)\r
-        def composite_where_clause(ids)\r
-          if ids.is_a?(String)\r
-            ids = [[ids]]\r
-          elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1\r
-            ids = [ids.to_composite_ids]\r
-          end\r
-          \r
-          ids.map do |id_set|\r
-            [primary_keys, id_set].transpose.map do |key, id|\r
-              "#{table_name}.#{key.to_s}=#{sanitize(id)}"\r
-            end.join(" AND ")\r
-          end.join(") OR (")       \r
-        end\r
-\r
-        # Returns true if the given +ids+ represents the primary keys of a record in the database, false otherwise.\r
-        # Example:\r
-        #   Person.exists?(5,7)\r
-        def exists?(ids)\r
-          if ids.is_a?(Array) && ids.first.is_a?(String)\r
-            count(:conditions => ids) > 0\r
-          else\r
-            obj = find(ids) rescue false\r
-            !obj.nil? and obj.is_a?(self)            \r
-          end\r
-        end\r
-\r
-        # Deletes the record with the given +ids+ without instantiating an object first, e.g. delete(1,2)\r
-        # If an array of ids is provided (e.g. delete([1,2], [3,4]), all of them\r
-        # are deleted.\r
-        def delete(*ids)\r
-          unless ids.is_a?(Array); raise "*ids must be an Array"; end\r
-          ids = [ids.to_composite_ids] if not ids.first.is_a?(Array)\r
-          where_clause = ids.map do |id_set|\r
-            [primary_keys, id_set].transpose.map do |key, id|\r
-              "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{sanitize(id)}"\r
-            end.join(" AND ")\r
-          end.join(") OR (")\r
-          delete_all([ "(#{where_clause})" ])\r
-        end\r
-\r
-        # Destroys the record with the given +ids+ by instantiating the object and calling #destroy (all the callbacks are the triggered).\r
-        # If an array of ids is provided, all of them are destroyed.\r
-        def destroy(*ids)\r
-          unless ids.is_a?(Array); raise "*ids must be an Array"; end\r
-          if ids.first.is_a?(Array)\r
-            ids = ids.map{|compids| compids.to_composite_ids}\r
-          else\r
-            ids = ids.to_composite_ids\r
-          end\r
-          ids.first.is_a?(CompositeIds) ? ids.each { |id_set| find(id_set).destroy } : find(ids).destroy\r
-        end\r
-\r
-        # Returns an array of column objects for the table associated with this class.\r
-        # Each column that matches to one of the primary keys has its\r
-        # primary attribute set to true\r
-        def columns\r
-          unless @columns\r
-            @columns = connection.columns(table_name, "#{name} Columns")\r
-            @columns.each {|column| column.primary = primary_keys.include?(column.name.to_sym)}\r
-          end\r
-          @columns\r
-        end\r
-\r
-        ## DEACTIVATED METHODS ##\r
-        public\r
-        # Lazy-set the sequence name to the connection's default.  This method\r
-        # is only ever called once since set_sequence_name overrides it.\r
-        def sequence_name #:nodoc:\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS\r
-        end\r
-\r
-        def reset_sequence_name #:nodoc:\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS\r
-        end\r
-\r
-        def set_primary_key(value = nil, &block)\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS\r
-        end\r
-\r
-        private\r
-        def find_one(id, options)\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS\r
-        end\r
-\r
-        def find_some(ids, options)\r
-          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS\r
-        end\r
-\r
-        def find_from_ids(ids, options)\r
-          ids = ids.first if ids.last == nil\r
-          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]\r
-          # if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)\r
-          # if ids is list of lists, then each inner list must follow rule above\r
-          if ids.first.is_a? String\r
-            # find '2,1' -> ids = ['2,1']\r
-            # find '2,1;7,3' -> ids = ['2,1;7,3']\r
-            ids = ids.first.split(ID_SET_SEP).map {|id_set| id_set.split(ID_SEP).to_composite_ids}\r
-            # find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds\r
-          end\r
-          ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)\r
-          ids.each do |id_set|\r
-            unless id_set.is_a?(Array)\r
-              raise "Ids must be in an Array, instead received: #{id_set.inspect}"\r
-            end\r
-            unless id_set.length == primary_keys.length\r
-              raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"\r
-            end\r
-          end\r
-\r
-          # Let keys = [:a, :b]\r
-          # If ids = [[10, 50], [11, 51]], then :conditions => \r
-          #   "(#{quoted_table_name}.a, #{quoted_table_name}.b) IN ((10, 50), (11, 51))"\r
-\r
-          conditions = ids.map do |id_set|\r
-            [primary_keys, id_set].transpose.map do |key, id|\r
-                               col = columns_hash[key.to_s]\r
-                               val = quote_value(id, col)\r
-              "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{val}"\r
-            end.join(" AND ")\r
-          end.join(") OR (")\r
-              \r
-          options.update :conditions => "(#{conditions})"\r
-\r
-          result = find_every(options)\r
-\r
-          if result.size == ids.size\r
-            ids.size == 1 ? result[0] : result\r
-          else\r
-            raise ::ActiveRecord::RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids.inspect})#{conditions}"\r
-          end\r
-        end\r
-      end\r
-    end\r
-  end\r
-end\r
-\r
-\r
-module ActiveRecord\r
-  ID_SEP     = ','\r
-  ID_SET_SEP = ';'\r
-\r
-  class Base\r
-    # Allows +attr_name+ to be the list of primary_keys, and returns the id\r
-    # of the object\r
-    # e.g. @object[@object.class.primary_key] => [1,1]\r
-    def [](attr_name)\r
-      if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first\r
-        attr_name = attr_name.split(ID_SEP)\r
-      end\r
-      attr_name.is_a?(Array) ?\r
-        attr_name.map {|name| read_attribute(name)} :\r
-        read_attribute(attr_name)\r
-    end\r
-\r
-    # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.\r
-    # (Alias for the protected write_attribute method).\r
-    def []=(attr_name, value)\r
-      if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first\r
-        attr_name = attr_name.split(ID_SEP)\r
-      end\r
-\r
-      if attr_name.is_a? Array\r
-        value = value.split(ID_SEP) if value.is_a? String\r
-        unless value.length == attr_name.length\r
-          raise "Number of attr_names and values do not match"\r
-        end\r
-        #breakpoint\r
-        [attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)}\r
-      else\r
-        write_attribute(attr_name, value)\r
-      end\r
-    end\r
-  end\r
-end\r
+module CompositePrimaryKeys
+  module ActiveRecord #:nodoc:
+    class CompositeKeyError < StandardError #:nodoc:
+    end
+
+    module Base #:nodoc:
+
+      INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
+      NOT_IMPLEMENTED_YET        = 'Not implemented for composite primary keys yet'
+
+      def self.append_features(base)
+        super
+        base.send(:include, InstanceMethods)
+        base.extend(ClassMethods)
+      end
+
+      module ClassMethods
+        def set_primary_keys(*keys)
+          keys = keys.first if keys.first.is_a?(Array)
+          keys = keys.map { |k| k.to_sym }
+          cattr_accessor :primary_keys
+          self.primary_keys = keys.to_composite_keys
+
+          class_eval <<-EOV
+            extend CompositeClassMethods
+            include CompositeInstanceMethods
+
+            include CompositePrimaryKeys::ActiveRecord::Associations
+            include CompositePrimaryKeys::ActiveRecord::AssociationPreload
+            include CompositePrimaryKeys::ActiveRecord::Calculations
+            include CompositePrimaryKeys::ActiveRecord::AttributeMethods
+          EOV
+        end
+
+        def composite?
+          false
+        end
+      end
+
+      module InstanceMethods
+        def composite?; self.class.composite?; end
+      end
+
+      module CompositeInstanceMethods
+
+        # A model instance's primary keys is always available as model.ids
+        # whether you name it the default 'id' or set it to something else.
+        def id
+          attr_names = self.class.primary_keys
+          CompositeIds.new(attr_names.map { |attr_name| read_attribute(attr_name) })
+        end
+        alias_method :ids, :id
+
+        def to_param
+          id.to_s
+        end
+
+        def id_before_type_cast #:nodoc:
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET
+        end
+
+        def quoted_id #:nodoc:
+          [self.class.primary_keys, ids].
+            transpose.
+            map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}.
+            to_composite_ids
+        end
+
+        # Sets the primary ID.
+        def id=(ids)
+          ids = ids.split(ID_SEP) if ids.is_a?(String)
+          ids.flatten!
+          unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
+            raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
+          end
+          [primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
+          id
+        end
+
+        # Returns a clone of the record that hasn't been assigned an id yet and
+        # is treated as a new record.  Note that this is a "shallow" clone:
+        # it copies the object's attributes only, not its associations.
+        # The extent of a "deep" clone is application-specific and is therefore
+        # left to the application to implement according to its need.
+        def clone
+          attrs = self.attributes_before_type_cast
+          self.class.primary_keys.each {|key| attrs.delete(key.to_s)}
+          self.class.new do |record|
+            record.send :instance_variable_set, '@attributes', attrs
+          end
+        end
+
+
+        private
+        # The xx_without_callbacks methods are overwritten as that is the end of the alias chain
+
+        # Creates a new record with values matching those of the instance attributes.
+        def create_without_callbacks
+          unless self.id
+            raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values"
+          end
+          attributes_minus_pks = attributes_with_quotes(false)
+          quoted_pk_columns = self.class.primary_key.map { |col| connection.quote_column_name(col) }
+          cols = quoted_column_names(attributes_minus_pks) << quoted_pk_columns
+          vals = attributes_minus_pks.values << quoted_id
+          connection.insert(
+            "INSERT INTO #{self.class.quoted_table_name} " +
+            "(#{cols.join(', ')}) " +
+            "VALUES (#{vals.join(', ')})",
+            "#{self.class.name} Create",
+            self.class.primary_key,
+            self.id
+          )
+          @new_record = false
+          return true
+        end
+
+        # Updates the associated record with values matching those of the instance attributes.
+        def update_without_callbacks
+          where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| 
+            "(#{connection.quote_column_name(pair[0])} = #{pair[1]})"
+          end
+          where_clause = where_clause_terms.join(" AND ")
+          connection.update(
+            "UPDATE #{self.class.quoted_table_name} " +
+            "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
+            "WHERE #{where_clause}",
+            "#{self.class.name} Update"
+          )
+          return true
+        end
+
+        # Deletes the record in the database and freezes this instance to reflect that no changes should
+        # be made (since they can't be persisted).
+        def destroy_without_callbacks
+          where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| 
+            "(#{connection.quote_column_name(pair[0])} = #{pair[1]})"
+          end
+          where_clause = where_clause_terms.join(" AND ")
+          unless new_record?
+            connection.delete(
+              "DELETE FROM #{self.class.quoted_table_name} " +
+              "WHERE #{where_clause}",
+              "#{self.class.name} Destroy"
+            )
+          end
+          freeze
+        end
+      end
+
+      module CompositeClassMethods
+        def primary_key; primary_keys; end
+        def primary_key=(keys); primary_keys = keys; end
+
+        def composite?
+          true
+        end
+
+        #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
+        #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
+        def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
+          many_ids.map {|ids| "#{left_bracket}#{ids}#{right_bracket}"}.join(list_sep)
+        end
+        
+        # Creates WHERE condition from list of composited ids
+        #   User.update_all({:role => 'admin'}, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> UPDATE admins SET admin.role='admin' WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2)
+        #   User.find(:all, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> SELECT * FROM admins WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2)
+        def composite_where_clause(ids)
+          if ids.is_a?(String)
+            ids = [[ids]]
+          elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1
+            ids = [ids.to_composite_ids]
+          end
+          
+          ids.map do |id_set|
+            [primary_keys, id_set].transpose.map do |key, id|
+              "#{table_name}.#{key.to_s}=#{sanitize(id)}"
+            end.join(" AND ")
+          end.join(") OR (")       
+        end
+
+        # Returns true if the given +ids+ represents the primary keys of a record in the database, false otherwise.
+        # Example:
+        #   Person.exists?(5,7)
+        def exists?(ids)
+          if ids.is_a?(Array) && ids.first.is_a?(String)
+            count(:conditions => ids) > 0
+          else
+            obj = find(ids) rescue false
+            !obj.nil? and obj.is_a?(self)            
+          end
+        end
+
+        # Deletes the record with the given +ids+ without instantiating an object first, e.g. delete(1,2)
+        # If an array of ids is provided (e.g. delete([1,2], [3,4]), all of them
+        # are deleted.
+        def delete(*ids)
+          unless ids.is_a?(Array); raise "*ids must be an Array"; end
+          ids = [ids.to_composite_ids] if not ids.first.is_a?(Array)
+          where_clause = ids.map do |id_set|
+            [primary_keys, id_set].transpose.map do |key, id|
+              "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{sanitize(id)}"
+            end.join(" AND ")
+          end.join(") OR (")
+          delete_all([ "(#{where_clause})" ])
+        end
+
+        # Destroys the record with the given +ids+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
+        # If an array of ids is provided, all of them are destroyed.
+        def destroy(*ids)
+          unless ids.is_a?(Array); raise "*ids must be an Array"; end
+          if ids.first.is_a?(Array)
+            ids = ids.map{|compids| compids.to_composite_ids}
+          else
+            ids = ids.to_composite_ids
+          end
+          ids.first.is_a?(CompositeIds) ? ids.each { |id_set| find(id_set).destroy } : find(ids).destroy
+        end
+
+        # Returns an array of column objects for the table associated with this class.
+        # Each column that matches to one of the primary keys has its
+        # primary attribute set to true
+        def columns
+          unless @columns
+            @columns = connection.columns(table_name, "#{name} Columns")
+            @columns.each {|column| column.primary = primary_keys.include?(column.name.to_sym)}
+          end
+          @columns
+        end
+
+        ## DEACTIVATED METHODS ##
+        public
+        # Lazy-set the sequence name to the connection's default.  This method
+        # is only ever called once since set_sequence_name overrides it.
+        def sequence_name #:nodoc:
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
+        end
+
+        def reset_sequence_name #:nodoc:
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
+        end
+
+        def set_primary_key(value = nil, &block)
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
+        end
+
+        private
+        def find_one(id, options)
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
+        end
+
+        def find_some(ids, options)
+          raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
+        end
+
+        def find_from_ids(ids, options)
+          ids = ids.first if ids.last == nil
+          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
+          # if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)
+          # if ids is list of lists, then each inner list must follow rule above
+          if ids.first.is_a? String
+            # find '2,1' -> ids = ['2,1']
+            # find '2,1;7,3' -> ids = ['2,1;7,3']
+            ids = ids.first.split(ID_SET_SEP).map {|id_set| id_set.split(ID_SEP).to_composite_ids}
+            # find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
+          end
+          ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
+          ids.each do |id_set|
+            unless id_set.is_a?(Array)
+              raise "Ids must be in an Array, instead received: #{id_set.inspect}"
+            end
+            unless id_set.length == primary_keys.length
+              raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
+            end
+          end
+
+          # Let keys = [:a, :b]
+          # If ids = [[10, 50], [11, 51]], then :conditions => 
+          #   "(#{quoted_table_name}.a, #{quoted_table_name}.b) IN ((10, 50), (11, 51))"
+
+          conditions = ids.map do |id_set|
+            [primary_keys, id_set].transpose.map do |key, id|
+                               col = columns_hash[key.to_s]
+                               val = quote_value(id, col)
+              "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{val}"
+            end.join(" AND ")
+          end.join(") OR (")
+              
+          options.update :conditions => "(#{conditions})"
+
+          result = find_every(options)
+
+          if result.size == ids.size
+            ids.size == 1 ? result[0] : result
+          else
+            raise ::ActiveRecord::RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids.inspect})#{conditions}"
+          end
+        end
+      end
+    end
+  end
+end
+
+
+module ActiveRecord
+  ID_SEP     = ','
+  ID_SET_SEP = ';'
+
+  class Base
+    # Allows +attr_name+ to be the list of primary_keys, and returns the id
+    # of the object
+    # e.g. @object[@object.class.primary_key] => [1,1]
+    def [](attr_name)
+      if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
+        attr_name = attr_name.split(ID_SEP)
+      end
+      attr_name.is_a?(Array) ?
+        attr_name.map {|name| read_attribute(name)} :
+        read_attribute(attr_name)
+    end
+
+    # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+    # (Alias for the protected write_attribute method).
+    def []=(attr_name, value)
+      if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
+        attr_name = attr_name.split(ID_SEP)
+      end
+
+      if attr_name.is_a? Array
+        value = value.split(ID_SEP) if value.is_a? String
+        unless value.length == attr_name.length
+          raise "Number of attr_names and values do not match"
+        end
+        #breakpoint
+        [attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)}
+      else
+        write_attribute(attr_name, value)
+      end
+    end
+  end
+end