Revert "Remove :finder_sql, :counter_sql, :insert_sql, :delete_sql."
This reverts commit 3803fcce26b837c0117f7d278b83c366dc4ed370. Conflicts: activerecord/CHANGELOG.md It will be deprecated only in 4.0, and removed properly in 4.1.
This commit is contained in:
parent
fb08039cb2
commit
7f3b475608
@ -1,51 +1,5 @@
|
||||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* AR::Relation#order: make new order prepend old one.
|
||||
|
||||
User.order("name asc").order("created_at desc")
|
||||
# SELECT * FROM users ORDER BY created_at desc, name asc
|
||||
|
||||
This also affects order defined in `default_scope` or any kind of associations.
|
||||
|
||||
*Bogdan Gusiev*
|
||||
|
||||
* `Model.all` now returns an `ActiveRecord::Relation`, rather than an
|
||||
array of records. Use `Model.to_a` or `Relation#to_a` if you really
|
||||
want an array.
|
||||
|
||||
In some specific cases, this may cause breakage when upgrading.
|
||||
However in most cases the `ActiveRecord::Relation` will just act as a
|
||||
lazy-loaded array and there will be no problems.
|
||||
|
||||
Note that calling `Model.all` with options (e.g.
|
||||
`Model.all(conditions: '...')` was already deprecated, but it will
|
||||
still return an array in order to make the transition easier.
|
||||
|
||||
`Model.scoped` is deprecated in favour of `Model.all`.
|
||||
|
||||
`Relation#all` still returns an array, but is deprecated (since it
|
||||
would serve no purpose if we made it return a `Relation`).
|
||||
|
||||
*Jon Leighton*
|
||||
|
||||
* Added an `update_columns` method. This new method updates the given attributes on an object,
|
||||
without calling save, hence skipping validations and callbacks.
|
||||
Example:
|
||||
|
||||
User.first.update_columns(name: "sebastian", age: 25) # => true
|
||||
|
||||
*Sebastian Martinez + Rafael Mendonça França*
|
||||
|
||||
* Removed `:finder_sql` and `:counter_sql` collection association options. Please
|
||||
use scopes instead.
|
||||
|
||||
*Jon Leighton*
|
||||
|
||||
* Removed `:insert_sql` and `:delete_sql` `has_and_belongs_to_many`
|
||||
association options. Please use `has_many :through` instead.
|
||||
|
||||
*Jon Leighton*
|
||||
|
||||
* The migration generator now creates a join table with (commented) indexes every time
|
||||
the migration name contains the word `join_table`:
|
||||
|
||||
|
@ -1108,6 +1108,15 @@ module ClassMethods
|
||||
# a +belongs_to+, and the records which get deleted are the join records, rather than
|
||||
# the associated records.
|
||||
#
|
||||
# [:finder_sql]
|
||||
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
|
||||
# associations that depend on multiple tables. May be supplied as a string or a proc where interpolation is
|
||||
# required. Note: When this option is used, +find_in_collection+
|
||||
# is _not_ added.
|
||||
# [:counter_sql]
|
||||
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
|
||||
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
|
||||
# replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
|
||||
# [:extend]
|
||||
# Specify a named module for extending the proxy. See "Association extensions".
|
||||
# [:include]
|
||||
@ -1179,6 +1188,14 @@ module ClassMethods
|
||||
# has_many :tags, :as => :taggable
|
||||
# has_many :reports, :readonly => true
|
||||
# has_many :subscribers, :through => :subscriptions, :source => :user
|
||||
# has_many :subscribers, :class_name => "Person", :finder_sql => Proc.new {
|
||||
# %Q{
|
||||
# SELECT DISTINCT *
|
||||
# FROM people p, post_subscriptions ps
|
||||
# WHERE ps.post_id = #{id} AND ps.person_id = p.id
|
||||
# ORDER BY p.first_name
|
||||
# }
|
||||
# }
|
||||
def has_many(name, scope = nil, options = {}, &extension)
|
||||
Builder::HasMany.build(self, name, scope, options, &extension)
|
||||
end
|
||||
@ -1542,6 +1559,18 @@ def belongs_to(name, scope = nil, options = {})
|
||||
# such as <tt>last_name, first_name DESC</tt>
|
||||
# [:uniq]
|
||||
# If true, duplicate associated objects will be ignored by accessors and query methods.
|
||||
# [:finder_sql]
|
||||
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
|
||||
# [:counter_sql]
|
||||
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
|
||||
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
|
||||
# replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
|
||||
# [:delete_sql]
|
||||
# Overwrite the default generated SQL statement used to remove links between the associated
|
||||
# classes with a manual statement.
|
||||
# [:insert_sql]
|
||||
# Overwrite the default generated SQL statement used to add links between the associated classes
|
||||
# with a manual statement.
|
||||
# [:extend]
|
||||
# Anonymous module for extending the proxy, see "Association extensions".
|
||||
# [:include]
|
||||
@ -1578,6 +1607,8 @@ def belongs_to(name, scope = nil, options = {})
|
||||
# has_and_belongs_to_many :nations, :class_name => "Country"
|
||||
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
|
||||
# has_and_belongs_to_many :categories, :readonly => true
|
||||
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
|
||||
# proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" }
|
||||
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
|
||||
Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
|
||||
end
|
||||
|
@ -140,6 +140,14 @@ def load_target
|
||||
reset
|
||||
end
|
||||
|
||||
def interpolate(sql, record = nil)
|
||||
if sql.respond_to?(:to_proc)
|
||||
owner.send(:instance_exec, record, &sql)
|
||||
else
|
||||
sql
|
||||
end
|
||||
end
|
||||
|
||||
# We can't dump @reflection since it contains the scope proc
|
||||
def marshal_dump
|
||||
reflection = @reflection
|
||||
|
@ -5,7 +5,7 @@ class AssociationScope #:nodoc:
|
||||
|
||||
attr_reader :association, :alias_tracker
|
||||
|
||||
delegate :klass, :owner, :reflection, :to => :association
|
||||
delegate :klass, :owner, :reflection, :interpolate, :to => :association
|
||||
delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
|
||||
|
||||
def initialize(association)
|
||||
|
@ -3,7 +3,7 @@ class CollectionAssociation < Association #:nodoc:
|
||||
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
||||
|
||||
def valid_options
|
||||
super + [:table_name, :before_add, :after_add, :before_remove, :after_remove]
|
||||
super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove]
|
||||
end
|
||||
|
||||
attr_reader :block_extension, :extension_module
|
||||
|
@ -5,7 +5,7 @@ def macro
|
||||
end
|
||||
|
||||
def valid_options
|
||||
super + [:join_table, :association_foreign_key]
|
||||
super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
|
||||
end
|
||||
|
||||
def build
|
||||
|
@ -44,7 +44,7 @@ def writer(records)
|
||||
|
||||
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
||||
def ids_reader
|
||||
if loaded?
|
||||
if loaded? || options[:finder_sql]
|
||||
load_target.map do |record|
|
||||
record.send(reflection.association_primary_key)
|
||||
end
|
||||
@ -79,7 +79,11 @@ def find(*args)
|
||||
if block_given?
|
||||
load_target.find(*args) { |*block_args| yield(*block_args) }
|
||||
else
|
||||
scoped.find(*args)
|
||||
if options[:finder_sql]
|
||||
find_by_scan(*args)
|
||||
else
|
||||
scoped.find(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -166,26 +170,35 @@ def sum(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# Count all records using SQL. Construct options and pass them with
|
||||
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
|
||||
# association, it will be used for the query. Otherwise, construct options and pass them with
|
||||
# scope to the target class's +count+.
|
||||
def count(column_name = nil, count_options = {})
|
||||
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
||||
|
||||
if association_scope.uniq_value
|
||||
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
||||
column_name ||= reflection.klass.primary_key
|
||||
count_options[:distinct] = true
|
||||
end
|
||||
if options[:counter_sql] || options[:finder_sql]
|
||||
unless count_options.blank?
|
||||
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
|
||||
end
|
||||
|
||||
value = scoped.count(column_name, count_options)
|
||||
|
||||
limit = options[:limit]
|
||||
offset = options[:offset]
|
||||
|
||||
if limit || offset
|
||||
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
||||
reflection.klass.count_by_sql(custom_counter_sql)
|
||||
else
|
||||
value
|
||||
if association_scope.uniq_value
|
||||
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
||||
column_name ||= reflection.klass.primary_key
|
||||
count_options[:distinct] = true
|
||||
end
|
||||
|
||||
value = scoped.count(column_name, count_options)
|
||||
|
||||
limit = options[:limit]
|
||||
offset = options[:offset]
|
||||
|
||||
if limit || offset
|
||||
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -310,6 +323,7 @@ def include?(record)
|
||||
if record.new_record?
|
||||
include_in_memory?(record)
|
||||
else
|
||||
load_target if options[:finder_sql]
|
||||
loaded? ? target.include?(record) : scoped.exists?(record)
|
||||
end
|
||||
else
|
||||
@ -344,8 +358,31 @@ def add_to_target(record)
|
||||
|
||||
private
|
||||
|
||||
def custom_counter_sql
|
||||
if options[:counter_sql]
|
||||
interpolate(options[:counter_sql])
|
||||
else
|
||||
# replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
|
||||
interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
|
||||
count_with = $2.to_s
|
||||
count_with = '*' if count_with.blank? || count_with =~ /,/
|
||||
"SELECT #{$1}COUNT(#{count_with}) FROM"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def custom_finder_sql
|
||||
interpolate(options[:finder_sql])
|
||||
end
|
||||
|
||||
def find_target
|
||||
records = scoped.to_a
|
||||
records =
|
||||
if options[:finder_sql]
|
||||
reflection.klass.find_by_sql(custom_finder_sql)
|
||||
else
|
||||
scoped.all
|
||||
end
|
||||
|
||||
records.each { |record| set_inverse_instance(record) }
|
||||
records
|
||||
end
|
||||
@ -484,6 +521,7 @@ def callbacks_for(callback_name)
|
||||
# Otherwise, go to the database only if none of the following are true:
|
||||
# * target already loaded
|
||||
# * owner is new record
|
||||
# * custom :finder_sql exists
|
||||
# * target contains new or changed record(s)
|
||||
# * the first arg is an integer (which indicates the number of records to be returned)
|
||||
def fetch_first_or_last_using_find?(args)
|
||||
@ -492,6 +530,7 @@ def fetch_first_or_last_using_find?(args)
|
||||
else
|
||||
!(loaded? ||
|
||||
owner.new_record? ||
|
||||
options[:finder_sql] ||
|
||||
target.any? { |record| record.new_record? || record.changed? } ||
|
||||
args.first.kind_of?(Integer))
|
||||
end
|
||||
@ -508,6 +547,20 @@ def include_in_memory?(record)
|
||||
end
|
||||
end
|
||||
|
||||
# If using a custom finder_sql, #find scans the entire collection.
|
||||
def find_by_scan(*args)
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
|
||||
|
||||
if ids.size == 1
|
||||
id = ids.first
|
||||
record = load_target.detect { |r| id == r.id }
|
||||
expects_array ? [ record ] : record
|
||||
else
|
||||
load_target.select { |r| ids.include?(r.id) }
|
||||
end
|
||||
end
|
||||
|
||||
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
||||
def first_or_last(type, *args)
|
||||
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
||||
|
@ -18,12 +18,16 @@ def insert_record(record, validate = true, raise = false)
|
||||
end
|
||||
end
|
||||
|
||||
stmt = join_table.compile_insert(
|
||||
join_table[reflection.foreign_key] => owner.id,
|
||||
join_table[reflection.association_foreign_key] => record.id
|
||||
)
|
||||
if options[:insert_sql]
|
||||
owner.connection.insert(interpolate(options[:insert_sql], record))
|
||||
else
|
||||
stmt = join_table.compile_insert(
|
||||
join_table[reflection.foreign_key] => owner.id,
|
||||
join_table[reflection.association_foreign_key] => record.id
|
||||
)
|
||||
|
||||
owner.connection.insert stmt
|
||||
owner.connection.insert stmt
|
||||
end
|
||||
|
||||
record
|
||||
end
|
||||
@ -35,17 +39,22 @@ def count_records
|
||||
end
|
||||
|
||||
def delete_records(records, method)
|
||||
relation = join_table
|
||||
condition = relation[reflection.foreign_key].eq(owner.id)
|
||||
if sql = options[:delete_sql]
|
||||
records = load_target if records == :all
|
||||
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
|
||||
else
|
||||
relation = join_table
|
||||
condition = relation[reflection.foreign_key].eq(owner.id)
|
||||
|
||||
unless records == :all
|
||||
condition = condition.and(
|
||||
relation[reflection.association_foreign_key]
|
||||
.in(records.map { |x| x.id }.compact)
|
||||
)
|
||||
unless records == :all
|
||||
condition = condition.and(
|
||||
relation[reflection.association_foreign_key]
|
||||
.in(records.map { |x| x.id }.compact)
|
||||
)
|
||||
end
|
||||
|
||||
owner.connection.delete(relation.where(condition).compile_delete)
|
||||
end
|
||||
|
||||
owner.connection.delete(relation.where(condition).compile_delete)
|
||||
end
|
||||
|
||||
def invertible_for?(record)
|
||||
|
@ -33,7 +33,13 @@ def insert_record(record, validate = true, raise = false)
|
||||
# If the collection is empty the target is set to an empty array and
|
||||
# the loaded flag is set to true as well.
|
||||
def count_records
|
||||
count = has_cached_counter? ? owner[cached_counter_attribute_name] : scoped.count
|
||||
count = if has_cached_counter?
|
||||
owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif options[:counter_sql] || options[:finder_sql]
|
||||
reflection.klass.count_by_sql(custom_counter_sql)
|
||||
else
|
||||
scoped.count
|
||||
end
|
||||
|
||||
# If there's nothing in the database and @target has no new records
|
||||
# we are certain the current target is an empty array. This is a
|
||||
|
@ -65,6 +65,16 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base
|
||||
:foreign_key => "developer_id"
|
||||
end
|
||||
|
||||
class DeveloperWithCounterSQL < ActiveRecord::Base
|
||||
self.table_name = 'developers'
|
||||
has_and_belongs_to_many :projects,
|
||||
:class_name => "DeveloperWithCounterSQL",
|
||||
:join_table => "developers_projects",
|
||||
:association_foreign_key => "project_id",
|
||||
:foreign_key => "developer_id",
|
||||
:counter_sql => proc { "SELECT COUNT(*) AS count_all FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE developers_projects.developer_id =#{id}" }
|
||||
end
|
||||
|
||||
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
|
||||
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings
|
||||
@ -351,6 +361,31 @@ def test_deleting_array
|
||||
assert_equal 0, david.projects(true).size
|
||||
end
|
||||
|
||||
def test_deleting_with_sql
|
||||
david = Developer.find(1)
|
||||
active_record = Project.find(1)
|
||||
active_record.developers.reload
|
||||
assert_equal 3, active_record.developers_by_sql.size
|
||||
|
||||
active_record.developers_by_sql.delete(david)
|
||||
assert_equal 2, active_record.developers_by_sql(true).size
|
||||
end
|
||||
|
||||
def test_deleting_array_with_sql
|
||||
active_record = Project.find(1)
|
||||
active_record.developers.reload
|
||||
assert_equal 3, active_record.developers_by_sql.size
|
||||
|
||||
active_record.developers_by_sql.delete(Developer.all)
|
||||
assert_equal 0, active_record.developers_by_sql(true).size
|
||||
end
|
||||
|
||||
def test_deleting_all_with_sql
|
||||
project = Project.find(1)
|
||||
project.developers_by_sql.delete_all
|
||||
assert_equal 0, project.developers_by_sql.size
|
||||
end
|
||||
|
||||
def test_deleting_all
|
||||
david = Developer.find(1)
|
||||
david.projects.reload
|
||||
@ -499,6 +534,25 @@ def test_include_returns_false_for_non_matching_record_to_verify_scoping
|
||||
assert ! project.developers.include?(developer)
|
||||
end
|
||||
|
||||
def test_find_in_association_with_custom_finder_sql
|
||||
assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find"
|
||||
|
||||
active_record = projects(:active_record)
|
||||
active_record.developers_with_finder_sql.reload
|
||||
assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
|
||||
end
|
||||
|
||||
def test_find_in_association_with_custom_finder_sql_and_multiple_interpolations
|
||||
# interpolate once:
|
||||
assert_equal [developers(:david), developers(:jamis), developers(:poor_jamis)], projects(:active_record).developers_with_finder_sql, "first interpolation"
|
||||
# interpolate again, for a different project id
|
||||
assert_equal [developers(:david)], projects(:action_controller).developers_with_finder_sql, "second interpolation"
|
||||
end
|
||||
|
||||
def test_find_in_association_with_custom_finder_sql_and_string_id
|
||||
assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
|
||||
end
|
||||
|
||||
def test_find_with_merged_options
|
||||
assert_equal 1, projects(:active_record).limited_developers.size
|
||||
assert_equal 1, projects(:active_record).limited_developers.to_a.size
|
||||
@ -734,6 +788,21 @@ def test_count
|
||||
assert_equal 2, david.projects.count
|
||||
end
|
||||
|
||||
def test_count_with_counter_sql
|
||||
developer = DeveloperWithCounterSQL.create(:name => 'tekin')
|
||||
developer.project_ids = [projects(:active_record).id]
|
||||
developer.save
|
||||
developer.reload
|
||||
assert_equal 1, developer.projects.count
|
||||
end
|
||||
|
||||
unless current_adapter?(:PostgreSQLAdapter)
|
||||
def test_count_with_finder_sql
|
||||
assert_equal 3, projects(:active_record).developers_with_finder_sql.count
|
||||
assert_equal 3, projects(:active_record).developers_with_multiline_finder_sql.count
|
||||
end
|
||||
end
|
||||
|
||||
def test_association_proxy_transaction_method_starts_transaction_in_association_class
|
||||
Post.expects(:transaction)
|
||||
Category.first.posts.transaction do
|
||||
|
@ -20,6 +20,43 @@
|
||||
require 'models/bulb'
|
||||
require 'models/engine'
|
||||
|
||||
class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
|
||||
class Invoice < ActiveRecord::Base
|
||||
has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items"
|
||||
end
|
||||
def test_should_fail
|
||||
assert_raise(ArgumentError) do
|
||||
Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase
|
||||
class Invoice < ActiveRecord::Base
|
||||
has_many :custom_line_items, :class_name => 'LineItem', :counter_sql => "SELECT COUNT(*) line_items.* from line_items"
|
||||
end
|
||||
def test_should_fail
|
||||
assert_raise(ArgumentError) do
|
||||
Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase
|
||||
class Invoice < ActiveRecord::Base
|
||||
has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items"
|
||||
end
|
||||
|
||||
def test_should_count_distinct_results
|
||||
invoice = Invoice.new
|
||||
invoice.custom_line_items << LineItem.new(:amount => 0)
|
||||
invoice.custom_line_items << LineItem.new(:amount => 0)
|
||||
invoice.save!
|
||||
|
||||
assert_equal 1, invoice.custom_line_items.count
|
||||
end
|
||||
end
|
||||
|
||||
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
|
||||
fixtures :authors, :posts, :comments
|
||||
|
||||
@ -270,6 +307,37 @@ def test_finding_using_primary_key
|
||||
assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name
|
||||
end
|
||||
|
||||
def test_finding_using_sql
|
||||
firm = Firm.scoped(:order => "id").first
|
||||
first_client = firm.clients_using_sql.first
|
||||
assert_not_nil first_client
|
||||
assert_equal "Microsoft", first_client.name
|
||||
assert_equal 1, firm.clients_using_sql.size
|
||||
assert_equal 1, Firm.scoped(:order => "id").first.clients_using_sql.size
|
||||
end
|
||||
|
||||
def test_finding_using_sql_take_into_account_only_uniq_ids
|
||||
firm = Firm.scoped(:order => "id").first
|
||||
client = firm.clients_using_sql.first
|
||||
assert_equal client, firm.clients_using_sql.find(client.id, client.id)
|
||||
assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s)
|
||||
end
|
||||
|
||||
def test_counting_using_sql
|
||||
assert_equal 1, Firm.scoped(:order => "id").first.clients_using_counter_sql.size
|
||||
assert Firm.scoped(:order => "id").first.clients_using_counter_sql.any?
|
||||
assert_equal 0, Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.size
|
||||
assert !Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.any?
|
||||
end
|
||||
|
||||
def test_counting_non_existant_items_using_sql
|
||||
assert_equal 0, Firm.scoped(:order => "id").first.no_clients_using_counter_sql.size
|
||||
end
|
||||
|
||||
def test_counting_using_finder_sql
|
||||
assert_equal 2, Firm.find(4).clients_using_sql.count
|
||||
end
|
||||
|
||||
def test_belongs_to_sanity
|
||||
c = Client.new
|
||||
assert_nil c.firm
|
||||
@ -297,6 +365,22 @@ def test_find_ids
|
||||
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
|
||||
end
|
||||
|
||||
def test_find_string_ids_when_using_finder_sql
|
||||
firm = Firm.scoped(:order => "id").first
|
||||
|
||||
client = firm.clients_using_finder_sql.find("2")
|
||||
assert_kind_of Client, client
|
||||
|
||||
client_ary = firm.clients_using_finder_sql.find(["2"])
|
||||
assert_kind_of Array, client_ary
|
||||
assert_equal client, client_ary.first
|
||||
|
||||
client_ary = firm.clients_using_finder_sql.find("2", "3")
|
||||
assert_kind_of Array, client_ary
|
||||
assert_equal 2, client_ary.size
|
||||
assert client_ary.include?(client)
|
||||
end
|
||||
|
||||
def test_find_all
|
||||
firm = Firm.all.merge!(:order => "id").first
|
||||
assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length
|
||||
@ -1124,6 +1208,13 @@ def test_get_ids_ignores_include_option
|
||||
assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids
|
||||
end
|
||||
|
||||
def test_get_ids_for_unloaded_finder_sql_associations_loads_them
|
||||
company = companies(:first_firm)
|
||||
assert !company.clients_using_sql.loaded?
|
||||
assert_equal [companies(:second_client).id], company.clients_using_sql_ids
|
||||
assert company.clients_using_sql.loaded?
|
||||
end
|
||||
|
||||
def test_get_ids_for_ordered_association
|
||||
assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
|
||||
end
|
||||
@ -1184,6 +1275,17 @@ def test_include_checks_if_record_exists_if_target_not_loaded
|
||||
assert ! firm.clients.loaded?
|
||||
end
|
||||
|
||||
def test_include_loads_collection_if_target_uses_finder_sql
|
||||
firm = companies(:first_firm)
|
||||
client = firm.clients_using_sql.first
|
||||
|
||||
firm.reload
|
||||
assert ! firm.clients_using_sql.loaded?
|
||||
assert firm.clients_using_sql.include?(client)
|
||||
assert firm.clients_using_sql.loaded?
|
||||
end
|
||||
|
||||
|
||||
def test_include_returns_false_for_non_matching_record_to_verify_scoping
|
||||
firm = companies(:first_firm)
|
||||
client = Client.create!(:name => 'Not Associated')
|
||||
|
@ -189,8 +189,8 @@ def test_association_reflection_in_modules
|
||||
|
||||
def test_reflection_of_all_associations
|
||||
# FIXME these assertions bust a lot
|
||||
assert_equal 34, Firm.reflect_on_all_associations.size
|
||||
assert_equal 24, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 39, Firm.reflect_on_all_associations.size
|
||||
assert_equal 29, Firm.reflect_on_all_associations(:has_many).size
|
||||
assert_equal 10, Firm.reflect_on_all_associations(:has_one).size
|
||||
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
|
||||
end
|
||||
|
@ -36,7 +36,9 @@ class Client < ::Company
|
||||
end
|
||||
|
||||
class Firm < Company
|
||||
has_many :clients, -> { order "id" }, :dependent => :destroy,
|
||||
has_many :clients, -> { order "id" }, :dependent => :destroy, :counter_sql =>
|
||||
"SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
|
||||
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )",
|
||||
:before_remove => :log_before_remove,
|
||||
:after_remove => :log_after_remove
|
||||
has_many :unsorted_clients, :class_name => "Client"
|
||||
@ -51,6 +53,17 @@ class Firm < Company
|
||||
has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, :class_name => "Client"
|
||||
has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client"
|
||||
has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client"
|
||||
has_many :clients_using_sql, :class_name => "Client", :finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" }
|
||||
has_many :clients_using_counter_sql, :class_name => "Client",
|
||||
:finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id} " },
|
||||
:counter_sql => proc { "SELECT COUNT(*) FROM companies WHERE client_of = #{id}" }
|
||||
has_many :clients_using_zero_counter_sql, :class_name => "Client",
|
||||
:finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" },
|
||||
:counter_sql => proc { "SELECT 0 FROM companies WHERE client_of = #{id}" }
|
||||
has_many :no_clients_using_counter_sql, :class_name => "Client",
|
||||
:finder_sql => 'SELECT * FROM companies WHERE client_of = 1000',
|
||||
:counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'
|
||||
has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1'
|
||||
has_many :plain_clients, :class_name => 'Client'
|
||||
has_many :readonly_clients, -> { readonly }, :class_name => 'Client'
|
||||
has_many :clients_using_primary_key, :class_name => 'Client',
|
||||
|
@ -11,6 +11,7 @@ class Firm < Company
|
||||
has_many :clients_sorted_desc, -> { order("id DESC") }, :class_name => "Client"
|
||||
has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client"
|
||||
has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client"
|
||||
has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
|
||||
|
||||
has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy
|
||||
end
|
||||
|
@ -7,6 +7,15 @@ class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").uniq }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').uniq }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => proc { "SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id} ORDER BY t.id" }
|
||||
has_and_belongs_to_many :developers_with_multiline_finder_sql, :class_name => "Developer", :finder_sql => proc {
|
||||
"SELECT
|
||||
t.*, j.*
|
||||
FROM
|
||||
developers_projects j,
|
||||
developers t WHERE t.id = j.developer_id AND j.project_id = #{id} ORDER BY t.id"
|
||||
}
|
||||
has_and_belongs_to_many :developers_by_sql, :class_name => "Developer", :delete_sql => proc { |record| "DELETE FROM developers_projects WHERE project_id = #{id} AND developer_id = #{record.id}" }
|
||||
has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id || '<new>'}"},
|
||||
:after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
|
||||
:before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
|
||||
|
Loading…
Reference in New Issue
Block a user