aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb10
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rwxr-xr-xactiverecord/lib/active_record/base.rb156
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb38
-rw-r--r--activerecord/lib/active_record/notifications.rb5
-rw-r--r--activerecord/lib/active_record/rails.rb59
-rw-r--r--activerecord/lib/active_record/relation.rb253
11 files changed, 336 insertions, 214 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 8dcb3a7711..2735bc5141 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
@@ -1717,9 +1717,9 @@ module ActiveRecord
select(column_aliases(join_dependency)).
group(construct_group(options[:group], options[:having], scope)).
order(construct_order(options[:order], scope)).
- conditions(construct_conditions(options[:conditions], scope))
+ where(construct_conditions(options[:conditions], scope))
- relation = relation.conditions(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+ 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)
relation
@@ -1757,7 +1757,7 @@ module ActiveRecord
end
relation = relation.joins(construct_join(options[:joins], scope)).
- conditions(construct_conditions(options[:conditions], scope)).
+ where(construct_conditions(options[:conditions], scope)).
group(construct_group(options[:group], options[:having], scope)).
order(construct_order(options[:order], scope)).
limit(construct_limit(options[:limit], scope)).
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 25e329c0c1..b85d76e8d3 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, :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!
@@ -383,7 +398,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/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index b01faa5212..9569b0c6f9 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -71,7 +71,7 @@ module ActiveRecord
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
relation = arel_table(@reflection.options[:join_table])
- relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id).
+ relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id)))
).delete
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index cd31b0e211..be74ddfcf0 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -70,7 +70,7 @@ module ActiveRecord
@reflection.klass.delete(records.map { |record| record.id })
else
relation = arel_table(@reflection.table_name)
- relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id).
+ relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
).update(relation[@reflection.primary_key_name] => nil)
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 321bba466e..2008dea5e9 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,6 +13,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/object/metaclass'
+require 'active_support/core_ext/module/delegation'
module ActiveRecord #:nodoc:
# Generic Active Record exception class.
@@ -650,6 +651,8 @@ module ActiveRecord #:nodoc:
end
end
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :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>.
def first(*args)
@@ -668,20 +671,10 @@ module ActiveRecord #:nodoc:
options = args.extract_options!
if options.empty? && !scoped?(:find)
- relation = arel_table
+ arel_table.to_a
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
+ construct_finder_arel_with_includes(options).to_a
end
- relation
end
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -882,7 +875,7 @@ module ActiveRecord #:nodoc:
relation = arel_table
if conditions = construct_conditions(conditions, scope)
- relation = relation.conditions(Arel::SqlLiteral.new(conditions))
+ relation = relation.where(Arel::SqlLiteral.new(conditions))
end
relation = if options.has_key?(:limit) || (scope && scope[:limit])
@@ -945,7 +938,7 @@ module ActiveRecord #:nodoc:
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
if conditions
- arel_table.conditions(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete
+ arel_table.where(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete
else
arel_table.delete
end
@@ -1514,13 +1507,8 @@ module ActiveRecord #:nodoc:
"(#{segments.join(') AND (')})" unless segments.empty?
end
-
def arel_table(table = nil)
- table = table_name if table.blank?
- if @arel_table.nil? || @arel_table.name != table
- @arel_table = Relation.new(self, Arel::Table.new(table))
- end
- @arel_table
+ Relation.new(self, Arel::Table.new(table || table_name))
end
private
@@ -1689,9 +1677,11 @@ module ActiveRecord #:nodoc:
def construct_finder_arel(options = {}, scope = scope(:find))
# TODO add lock to Arel
+ validate_find_options(options)
+
relation = arel_table(options[:from]).
joins(construct_join(options[:joins], scope)).
- conditions(construct_conditions(options[:conditions], 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)).
@@ -1703,6 +1693,21 @@ module ActiveRecord #:nodoc:
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
@@ -1837,9 +1842,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>.
@@ -1855,103 +1859,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
@@ -2566,7 +2478,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).delete
+ self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete
end
@destroyed = true
@@ -2853,7 +2765,7 @@ module ActiveRecord #:nodoc:
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
- self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 40242333e5..fcba23dc0d 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -148,7 +148,7 @@ module ActiveRecord
else
relation = arel_table(options[:from]).
joins(construct_join(options[:joins], scope)).
- conditions(construct_conditions(options[:conditions], scope)).
+ where(construct_conditions(options[:conditions], scope)).
order(options[:order]).
limit(options[:limit]).
offset(options[:offset])
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index bbe2d1f205..a6336e762a 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -6,18 +6,34 @@ module ActiveRecord
module NamedScope
extend ActiveSupport::Concern
- # All subclasses of ActiveRecord::Base have one named scope:
- # * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
- #
- # These anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
- # intermediate values (scopes) around as first-class objects is convenient.
- #
- # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
- included do
- named_scope :scoped, lambda { |scope| scope }
- end
-
module ClassMethods
+ # Returns a relation if invoked without any arguments.
+ #
+ # posts = Post.scoped
+ # posts.size # Fires "select count(*) from posts" and returns the count
+ # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
+ #
+ # Returns an anonymous named scope if any options are supplied.
+ #
+ # shirts = Shirt.scoped(:conditions => {:color => 'red'})
+ # shirts = shirts.scoped(:include => :washing_instructions)
+ #
+ # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
+ # intermediate values (scopes) around as first-class objects is convenient.
+ #
+ # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
+ def scoped(options = {}, &block)
+ if options.present?
+ Scope.new(self, options, &block)
+ else
+ unless scoped?(:find)
+ finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table
+ else
+ construct_finder_arel_with_includes
+ end
+ end
+ end
+
def scopes
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end
diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb
deleted file mode 100644
index 562a5b91f4..0000000000
--- a/activerecord/lib/active_record/notifications.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'active_support/notifications'
-
-ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload|
- ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before)
-end
diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb
new file mode 100644
index 0000000000..ddbc555113
--- /dev/null
+++ b/activerecord/lib/active_record/rails.rb
@@ -0,0 +1,59 @@
+# For now, action_controller must always be present with
+# rails, so let's make sure that it gets required before
+# here. This is needed for correctly setting up the middleware.
+# In the future, this might become an optional require.
+require "action_controller/rails"
+
+module ActiveRecord
+ class Plugin < Rails::Plugin
+ plugin_name :active_record
+
+ initializer "active_record.set_configs" do |app|
+ app.config.active_record.each do |k,v|
+ ActiveRecord::Base.send "#{k}=", v
+ end
+ end
+
+ # This sets the database configuration from Configuration#database_configuration
+ # and then establishes the connection.
+ initializer "active_record.initialize_database" do |app|
+ ActiveRecord::Base.configurations = app.config.database_configuration
+ ActiveRecord::Base.establish_connection
+ end
+
+ initializer "active_record.initialize_timezone" do
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ end
+
+ # Setup database middleware after initializers have run
+ initializer "active_record.initialize_database_middleware" do |app|
+ middleware = app.config.middleware
+ if middleware.include?(ActiveRecord::SessionStore)
+ middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::ConnectionAdapters::ConnectionManagement
+ middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::QueryCache
+ else
+ middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement
+ middleware.use ActiveRecord::QueryCache
+ end
+ end
+
+ initializer "active_record.load_observers" do
+ ActiveRecord::Base.instantiate_observers
+ end
+
+ # TODO: ActiveRecord::Base.logger should delegate to its own config.logger
+ initializer "active_record.logger" do
+ ActiveRecord::Base.logger ||= Rails.logger
+ end
+
+ initializer "active_record.notifications" do
+ require 'active_support/notifications'
+
+ ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload|
+ ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before)
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5f0eec754f..c927270eee 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -4,37 +4,94 @@ module ActiveRecord
delegate :length, :collect, :find, :map, :each, :to => :to_a
attr_reader :relation, :klass
- def initialize(klass, relation)
+ def initialize(klass, relation, readonly = false, preload = [], eager_load = [])
@klass, @relation = klass, relation
- @readonly = false
- @associations_to_preload = []
- @eager_load_associations = []
+ @readonly = readonly
+ @associations_to_preload = preload
+ @eager_load_associations = eager_load
+ @loaded = false
end
- def preload(association)
- @associations_to_preload += association
- self
+ def preload(*associations)
+ create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations))
end
- def eager_load(association)
- @eager_load_associations += association
- self
+ def eager_load(*associations)
+ create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations))
end
def readonly
- @readonly = true
- self
+ create_new_relation(@relation, true)
+ end
+
+ def select(selects)
+ create_new_relation(@relation.project(selects))
+ end
+
+ def group(groups)
+ create_new_relation(@relation.group(groups))
+ end
+
+ def order(orders)
+ create_new_relation(@relation.order(orders))
+ end
+
+ def limit(limits)
+ create_new_relation(@relation.take(limits))
+ end
+
+ def offset(offsets)
+ create_new_relation(@relation.skip(offsets))
+ end
+
+ def on(join)
+ create_new_relation(@relation.on(join))
+ end
+
+ def joins(join, join_type = nil)
+ return self 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(@klass.send(:build_association_joins, join))
+ end
+ else
+ @relation.join(join, join_type)
+ end
+
+ create_new_relation(join_relation)
+ end
+
+ def where(*args)
+ if [String, Hash, Array].include?(args.first.class)
+ conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
+ else
+ conditions = args.first
+ end
+
+ create_new_relation(@relation.where(conditions))
+ end
+
+ def respond_to?(method)
+ @relation.respond_to?(method) || Array.method_defined?(method) || super
end
def to_a
- records = if @eager_load_associations.any?
+ return @records if loaded?
+
+ @records = if @eager_load_associations.any?
catch :invalid_query do
return @klass.send(:find_with_associations, {
:select => @relation.send(:select_clauses).join(', '),
: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
},
@@ -45,83 +102,151 @@ module ActiveRecord
@klass.find_by_sql(@relation.to_sql)
end
- @klass.send(:preload_associations, records, @associations_to_preload) unless @associations_to_preload.empty?
- records.each { |record| record.readonly! } if @readonly
+ @associations_to_preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
+ @records.each { |record| record.readonly! } if @readonly
- records
+ @loaded = true
+ @records
end
- def first
- @relation = @relation.take(1)
- to_a.first
- end
+ alias all to_a
- def select(selects)
- selects.blank? ? self : Relation.new(@klass, @relation.project(selects))
- end
+ def find(*ids, &block)
+ return to_a.find(&block) if block_given?
- def group(groups)
- groups.blank? ? self : Relation.new(@klass, @relation.group(groups))
+ 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 order(orders)
- orders.blank? ? self : Relation.new(@klass, @relation.order(orders))
+ def first
+ if loaded?
+ @records.first
+ else
+ @first ||= limit(1).to_a[0]
+ end
end
- def limit(limits)
- limits.blank? ? self : Relation.new(@klass, @relation.take(limits))
+ def loaded?
+ @loaded
end
- def offset(offsets)
- offsets.blank? ? self : Relation.new(@klass, @relation.skip(offsets))
+ def reload
+ @loaded = false
+ @records = @first = nil
+ self
end
- def on(join)
- join.blank? ? self : Relation.new(@klass, @relation.on(join))
+ 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 joins(join, join_type = nil)
- if join.blank?
- self
+ 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
- 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
- else
- @relation.join(join, join_type)
- end
- Relation.new(@klass, join)
+ result
end
end
- def conditions(conditions)
- if conditions.blank?
- self
+ 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
- conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class)
- Relation.new(@klass, @relation.where(conditions))
+ 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 respond_to?(method)
- @relation.respond_to?(method) || Array.method_defined?(method) || super
+ 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
- private
- 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)
+ def find_some(ids)
+ result = where(@klass.primary_key => ids).all
+
+ expected_size =
+ if @relation.taken && ids.size > @relation.taken
+ @relation.taken
else
- super
+ 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, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations)
+ Relation.new(@klass, relation, readonly, preload, eager_load)
+ end
+
+ def where_clause(join_string = "\n\tAND ")
+ @relation.send(:where_clauses).join(join_string)
+ end
end
end