diff options
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 9 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/association_collection.rb | 51 | ||||
-rw-r--r-- | activerecord/lib/active_record/autosave_association.rb | 4 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 301 | ||||
-rw-r--r-- | activerecord/lib/active_record/named_scope.rb | 17 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 225 |
6 files changed, 288 insertions, 319 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0ed46046ff..c23c9f63f1 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1382,9 +1382,9 @@ module ActiveRecord if reflection.through_reflection && reflection.source_reflection.belongs_to? through = reflection.through_reflection primary_key = reflection.source_reflection.primary_key_name - send(through.name).all(:select => "DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") + send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) end end end @@ -1707,7 +1707,7 @@ module ActiveRecord def construct_finder_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table((scope && scope[:from]) || options[:from]) + relation = arel_table for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1717,7 +1717,8 @@ module ActiveRecord select(column_aliases(join_dependency)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). - where(construct_conditions(options[:conditions], scope)) + where(construct_conditions(options[:conditions], scope)). + from((scope && scope[:from]) || options[:from]) relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 25e329c0c1..d2c61cdc78 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -20,7 +20,22 @@ module ActiveRecord super construct_sql end - + + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped + + def select(select = nil, &block) + if block_given? + load_target + @target.select(&block) + else + scoped.select(select) + end + end + + def scoped + with_scope(construct_scope) { @reflection.klass.scoped } + end + def find(*args) options = args.extract_options! @@ -37,27 +52,21 @@ module ActiveRecord load_target.select { |r| ids.include?(r.id) } end else - conditions = "#{@finder_sql}" - if sanitized_conditions = sanitize_sql(options[:conditions]) - conditions << " AND (#{sanitized_conditions})" - end - - options[:conditions] = conditions + merge_options_from_reflection!(options) + construct_find_options!(options) + + find_scope = construct_scope[:find].slice(:conditions, :order) - if options[:order] && @reflection.options[:order] - options[:order] = "#{options[:order]}, #{@reflection.options[:order]}" - elsif @reflection.options[:order] - options[:order] = @reflection.options[:order] + with_scope(:find => find_scope) do + relation = @reflection.klass.send(:construct_finder_arel_with_includes, options) + + case args.first + when :first, :last, :all + relation.send(args.first) + else + relation.find(*args) + end end - - # Build options specific to association - construct_find_options!(options) - - merge_options_from_reflection!(options) - - # Pass through args exactly as we received them. - args << options - @reflection.klass.find(*args) end end @@ -383,7 +392,7 @@ module ActiveRecord loaded if target target end - + def method_missing(method, *args) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) if block_given? diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 8f37fcd515..c0d8904bc8 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -221,9 +221,9 @@ module ActiveRecord if new_record association elsif association.loaded? - autosave ? association : association.select { |record| record.new_record? } + autosave ? association : association.find_all { |record| record.new_record? } else - autosave ? association.target : association.target.select { |record| record.new_record? } + autosave ? association.target : association.target.find_all { |record| record.new_record? } end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3c41d16f63..3b880ce17f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -640,18 +640,19 @@ module ActiveRecord #:nodoc: # end def find(*args) options = args.extract_options! - validate_find_options(options) set_readonly_option!(options) + relation = construct_finder_arel_with_includes(options) + case args.first - when :first then find_initial(options) - when :last then find_last(options) - when :all then find_every(options) - else find_from_ids(args, options) + when :first, :last, :all + relation.send(args.first) + else + relation.find(*args) end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :arel_table + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. @@ -665,26 +666,10 @@ module ActiveRecord #:nodoc: find(:last, *args) end - # Returns an ActiveRecord::Relation object. You can pass in all the same arguments to this method as you can - # to find(:all). + # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the + # same arguments to this method as you can to <tt>find(:all)</tt>. def all(*args) - options = args.extract_options! - - if options.empty? && !scoped?(:find) - relation = arel_table - else - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation.eager_load(include_associations) - else - relation.preload(include_associations) - end - end - end - relation + find(:all, *args) end # Executes a custom SQL query against your database and returns all the results. The results will @@ -738,10 +723,13 @@ module ActiveRecord #:nodoc: # Person.exists?(:name => "David") # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? - def exists?(id_or_conditions = {}) - find_initial( - :select => "#{quoted_table_name}.#{primary_key}", - :conditions => expand_id_conditions(id_or_conditions)) ? true : false + def exists?(id_or_conditions = nil) + case id_or_conditions + when Array, Hash + where(id_or_conditions).exists? + else + scoped.exists?(id_or_conditions) + end end # Creates an object (or multiple objects) and saves it to the database, if validations pass. @@ -926,7 +914,7 @@ module ActiveRecord #:nodoc: # Person.destroy_all("last_login < '2004-04-04'") # Person.destroy_all(:status => "inactive") def destroy_all(conditions = nil) - find(:all, :conditions => conditions).each { |object| object.destroy } + where(conditions).destroy_all end # Deletes the records matching +conditions+ without instantiating the records first, and hence not @@ -1522,120 +1510,6 @@ module ActiveRecord #:nodoc: end private - def find_initial(options) - options.update(:limit => 1) - find_every(options).first - end - - def find_last(options) - order = options[:order] - - if order - order = reverse_sql_order(order) - elsif !scoped?(:find, :order) - order = "#{table_name}.#{primary_key} DESC" - end - - if scoped?(:find, :order) - scope = scope(:find) - original_scoped_order = scope[:order] - scope[:order] = reverse_sql_order(original_scoped_order) - end - - begin - find_initial(options.merge({ :order => order })) - ensure - scope[:order] = original_scoped_order if original_scoped_order - end - end - - def reverse_sql_order(order_query) - order_query.to_s.split(/,/).each { |s| - if s.match(/\s(asc|ASC)$/) - s.gsub!(/\s(asc|ASC)$/, ' DESC') - elsif s.match(/\s(desc|DESC)$/) - s.gsub!(/\s(desc|DESC)$/, ' ASC') - else - s.concat(' DESC') - end - }.join(',') - end - - def find_every(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? && references_eager_loaded_tables?(options) - records = find_with_associations(options) - else - records = find_by_sql(construct_finder_sql(options)) - if include_associations.any? - preload_associations(records, include_associations) - end - end - - records.each { |record| record.readonly! } if options[:readonly] - - records - end - - def find_from_ids(ids, options) - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? - - ids = ids.flatten.compact.uniq - - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{name} without an ID" - when 1 - result = find_one(ids.first, options) - expects_array ? [ result ] : result - else - find_some(ids, options) - end - end - - def find_one(id, options) - conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] - options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}" - - # Use find_every(options).first since the primary key condition - # already ensures we have a single record. Using find_initial adds - # a superfluous :limit => 1. - if result = find_every(options).first - result - else - raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}" - end - end - - def find_some(ids, options) - conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] - ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',') - options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}" - - result = find_every(options) - - # Determine expected size from limit and offset, not just ids.size. - expected_size = - if options[:limit] && ids.size > options[:limit] - options[:limit] - else - ids.size - end - - # 11 ids with limit 3, offset 9 should give 2 results. - if options[:offset] && (ids.size - options[:offset] < expected_size) - expected_size = ids.size - options[:offset] - end - - if result.size == expected_size - result - else - raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" - end - end - # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. @@ -1687,20 +1561,38 @@ module ActiveRecord #:nodoc: def construct_finder_arel(options = {}, scope = scope(:find)) # TODO add lock to Arel - relation = arel_table(options[:from]). + validate_find_options(options) + + relation = arel_table. joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). - offset(construct_offset(options[:offset], scope)) + offset(construct_offset(options[:offset], scope)). + from(options[:from]) relation = relation.readonly if options[:readonly] relation end + def construct_finder_arel_with_includes(options = {}) + relation = construct_finder_arel(options) + include_associations = merge_includes(scope(:find, :include), options[:include]) + + if include_associations.any? + if references_eager_loaded_tables?(options) + relation = relation.eager_load(include_associations) + else + relation = relation.preload(include_associations) + end + end + + relation + end + def construct_finder_sql(options, scope = scope(:find)) construct_finder_arel(options, scope).to_sql end @@ -1735,6 +1627,7 @@ module ActiveRecord #:nodoc: def construct_order(order, scope) orders = [] + scoped_order = scope[:order] if scope if order orders << order @@ -1742,7 +1635,8 @@ module ActiveRecord #:nodoc: elsif scoped_order orders << scoped_order end - orders + + orders.reject {|o| o.blank?} end def construct_limit(limit, scope) @@ -1835,9 +1729,8 @@ module ActiveRecord #:nodoc: end # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt> - # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and - # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for - # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>. + # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt> + # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>. # # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ # is actually <tt>find_all_by_amount(amount, options)</tt>. @@ -1853,103 +1746,11 @@ module ActiveRecord #:nodoc: attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) if match.finder? - finder = match.finder - bang = match.bang? - # def self.find_by_login_and_activated(*args) - # options = args.extract_options! - # attributes = construct_attributes_from_arguments( - # [:login,:activated], - # args - # ) - # finder_options = { :conditions => attributes } - # validate_find_options(options) - # set_readonly_option!(options) - # - # if options[:conditions] - # with_scope(:find => finder_options) do - # find(:first, options) - # end - # else - # find(:first, options.merge(finder_options)) - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - options = args.extract_options! - attributes = construct_attributes_from_arguments( - [:#{attribute_names.join(',:')}], - args - ) - finder_options = { :conditions => attributes } - validate_find_options(options) - set_readonly_option!(options) - - #{'result = ' if bang}if options[:conditions] - with_scope(:find => finder_options) do - find(:#{finder}, options) - end - else - find(:#{finder}, options.merge(finder_options)) - end - #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect { |pair| pair.join(\' = \') }.join(\', \')}")' if bang} - end - }, __FILE__, __LINE__ - send(method_id, *arguments) + options = arguments.extract_options! + relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? - instantiator = match.instantiator - # def self.find_or_create_by_user_id(*args) - # guard_protected_attributes = false - # - # if args[0].is_a?(Hash) - # guard_protected_attributes = true - # attributes = args[0].with_indifferent_access - # find_attributes = attributes.slice(*[:user_id]) - # else - # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args) - # end - # - # options = { :conditions => find_attributes } - # set_readonly_option!(options) - # - # record = find(:first, options) - # - # if record.nil? - # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - # yield(record) if block_given? - # record.save - # record - # else - # record - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - guard_protected_attributes = false - - if args[0].is_a?(Hash) - guard_protected_attributes = true - attributes = args[0].with_indifferent_access - find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}]) - else - find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) - end - - options = { :conditions => find_attributes } - set_readonly_option!(options) - - record = find(:first, options) - - if record.nil? - record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - #{'yield(record) if block_given?'} - #{'record.save' if instantiator == :create} - record - else - record - end - end - }, __FILE__, __LINE__ - send(method_id, *arguments, &block) + scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end elsif match = DynamicScopeMatch.match(method_id) attribute_names = match.attribute_names @@ -2011,14 +1812,6 @@ module ActiveRecord #:nodoc: end end - # Interpret Array and Hash as conditions and anything else as an id. - def expand_id_conditions(id_or_conditions) - case id_or_conditions - when Array, Hash then id_or_conditions - else sanitize_sql(primary_key => id_or_conditions) - end - end - protected # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. # method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>, diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 38d54fa8ec..a6336e762a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -26,22 +26,11 @@ module ActiveRecord if options.present? Scope.new(self, options, &block) else - if !scoped?(:find) - relation = arel_table + unless scoped?(:find) + finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table else - relation = construct_finder_arel - include_associations = scope(:find, :include) - - if include_associations.present? - if references_eager_loaded_tables?(options) - relation.eager_load(include_associations) - else - relation.preload(include_associations) - end - end + construct_finder_arel_with_includes end - - relation end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 853103a606..530402bf5d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,8 +1,8 @@ module ActiveRecord class Relation delegate :to_sql, :to => :relation - delegate :length, :collect, :find, :map, :each, :to => :to_a - attr_reader :relation, :klass + delegate :length, :collect, :map, :each, :to => :to_a + attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations def initialize(klass, relation, readonly = false, preload = [], eager_load = []) @klass, @relation = klass, relation @@ -12,6 +12,21 @@ module ActiveRecord @loaded = false end + def merge(r) + joins(r.relation.joins(r.relation)). + group(r.send(:group_clauses).join(', ')). + order(r.send(:order_clauses).join(', ')). + where(r.send(:where_clause)). + limit(r.taken). + offset(r.skipped). + select(r.send(:select_clauses).join(', ')). + eager_load(r.eager_load_associations). + preload(r.associations_to_preload). + from(r.send(:sources).any? ? r.send(:from_clauses) : nil) + end + + alias :& :merge + def preload(*associations) create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) end @@ -25,23 +40,39 @@ module ActiveRecord end def select(selects) - create_new_relation(@relation.project(selects)) + selects.present? ? create_new_relation(@relation.project(selects)) : create_new_relation + end + + def from(from) + from.present? ? create_new_relation(@relation.from(from)) : create_new_relation end def group(groups) - create_new_relation(@relation.group(groups)) + groups.present? ? create_new_relation(@relation.group(groups)) : create_new_relation end def order(orders) - create_new_relation(@relation.order(orders)) + orders.present? ? create_new_relation(@relation.order(orders)) : create_new_relation + end + + def reverse_order + relation = create_new_relation + relation.instance_variable_set(:@orders, nil) + + order_clause = @relation.send(:order_clauses).join(', ') + if order_clause.present? + relation.order(reverse_sql_order(order_clause)) + else + relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") + end end def limit(limits) - create_new_relation(@relation.take(limits)) + limits.present? ? create_new_relation(@relation.take(limits)) : create_new_relation end def offset(offsets) - create_new_relation(@relation.skip(offsets)) + offsets.present? ? create_new_relation(@relation.skip(offsets)) : create_new_relation end def on(join) @@ -49,22 +80,27 @@ module ActiveRecord end def joins(join, join_type = nil) - join = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) - else - @relation.join(@klass.send(:build_association_joins, join)) - end + return create_new_relation if join.blank? + + join_relation = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) else - @relation.join(join, join_type) + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) end - create_new_relation(join) + + create_new_relation(join_relation) end def where(*args) + return create_new_relation if args.blank? + if [String, Hash, Array].include?(args.first.class) conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) else @@ -88,9 +124,10 @@ module ActiveRecord :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), :order => @relation.send(:order_clauses).join(', '), - :conditions => @relation.send(:where_clauses).join("\n\tAND "), + :conditions => where_clause, :limit => @relation.taken, - :offset => @relation.skipped + :offset => @relation.skipped, + :from => (@relation.send(:from_clauses) if @relation.send(:sources).any?) }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) end @@ -108,6 +145,31 @@ module ActiveRecord alias all to_a + def find(*ids, &block) + return to_a.find(&block) if block_given? + + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + end + + def exists?(id = nil) + relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) + relation = relation.where(@klass.primary_key => id) if id + relation.first ? true : false + end + def first if loaded? @records.first @@ -116,30 +178,145 @@ module ActiveRecord end end + def last + if loaded? + @records.last + else + @last ||= reverse_order.limit(1).to_a[0] + end + end + + def destroy_all + to_a.each {|object| object.destroy} + reset + end + def loaded? @loaded end def reload @loaded = false - @records = @first = nil + reset + end + + def reset + @first = @last = nil + @records = [] self end - private + protected def method_missing(method, *args, &block) if @relation.respond_to?(method) @relation.send(method, *args, &block) elsif Array.method_defined?(method) to_a.send(method, *args, &block) + elsif match = DynamicFinderMatch.match(method) + attributes = match.attribute_names + super unless @klass.send(:all_attributes_exists?, attributes) + + if match.finder? + find_by_attributes(match, attributes, *args) + elsif match.instantiator? + find_or_instantiator_by_attributes(match, attributes, *args, &block) + end else super end end - def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) - Relation.new(@klass, relation, readonly, preload, eager_load) + def find_by_attributes(match, attributes, *args) + conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + result = where(conditions).send(match.finder) + + if match.bang? && result.blank? + raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" + else + result + end + end + + def find_or_instantiator_by_attributes(match, attributes, *args) + guard_protected_attributes = false + + if args[0].is_a?(Hash) + guard_protected_attributes = true + attributes_for_create = args[0].with_indifferent_access + conditions = attributes_for_create.slice(*attributes).symbolize_keys + else + attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + end + + record = where(conditions).first + + unless record + record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) } + yield(record) if block_given? + record.save if match.instantiator == :create + end + + record + end + + def find_one(id) + record = where(@klass.primary_key => id).first + + unless record + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" + end + + record + end + + def find_some(ids) + result = where(@klass.primary_key => ids).all + + expected_size = + if @relation.taken && ids.size > @relation.taken + @relation.taken + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if @relation.skipped && (ids.size - @relation.skipped < expected_size) + expected_size = ids.size - @relation.skipped + end + + if result.size == expected_size + result + else + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + + error = "Couldn't find all #{@klass.name.pluralize} with IDs " + error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" + raise RecordNotFound, error + end + end + + def create_new_relation(relation = @relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) + self.class.new(@klass, relation, readonly, preload, eager_load) + end + + def where_clause(join_string = "\n\tAND ") + @relation.send(:where_clauses).join(join_string) + end + + def reverse_sql_order(order_query) + order_query.to_s.split(/,/).each { |s| + if s.match(/\s(asc|ASC)$/) + s.gsub!(/\s(asc|ASC)$/, ' DESC') + elsif s.match(/\s(desc|DESC)$/) + s.gsub!(/\s(desc|DESC)$/, ' ASC') + else + s.concat(' DESC') + end + }.join(',') end end |