mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
refactor dynamic finder name matching into its own class
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
This commit is contained in:
parent
3beed9cdb7
commit
143f5fbb21
4 changed files with 125 additions and 72 deletions
|
@ -51,6 +51,7 @@ require 'active_record/calculations'
|
|||
require 'active_record/serialization'
|
||||
require 'active_record/attribute_methods'
|
||||
require 'active_record/dirty'
|
||||
require 'active_record/dynamic_finder_match'
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
extend ActiveRecord::QueryCache
|
||||
|
|
|
@ -1354,8 +1354,8 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def respond_to?(method_id, include_private = false)
|
||||
if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
|
||||
return true if all_attributes_exists?(extract_attribute_names_from_match(match))
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
@ -1674,88 +1674,65 @@ module ActiveRecord #:nodoc:
|
|||
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# attempts to use it do not run through method_missing.
|
||||
def method_missing(method_id, *arguments)
|
||||
if match = matches_dynamic_finder?(method_id)
|
||||
finder = determine_finder(match)
|
||||
|
||||
attribute_names = extract_attribute_names_from_match(match)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
if match.finder?
|
||||
finder = match.finder
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
if options[:conditions]
|
||||
with_scope(:find => finder_options) do
|
||||
ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
|
||||
if options[:conditions]
|
||||
with_scope(:find => finder_options) do
|
||||
ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
|
||||
end
|
||||
else
|
||||
ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
|
||||
end
|
||||
else
|
||||
ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
|
||||
end
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
|
||||
instantiator = determine_instantiator(match)
|
||||
attribute_names = extract_attribute_names_from_match(match)
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
elsif match.instantiator?
|
||||
instantiator = match.instantiator
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args)
|
||||
guard_protected_attributes = false
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
options = { :conditions => find_attributes }
|
||||
set_readonly_option!(options)
|
||||
|
||||
record = find_initial(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
|
||||
|
||||
options = { :conditions => find_attributes }
|
||||
set_readonly_option!(options)
|
||||
|
||||
record = find_initial(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)
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def matches_dynamic_finder?(method_id)
|
||||
/^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
||||
end
|
||||
|
||||
def matches_dynamic_finder_with_initialize_or_create?(method_id)
|
||||
/^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
||||
end
|
||||
|
||||
def determine_finder(match)
|
||||
match.captures.first == 'all_by' ? :find_every : :find_initial
|
||||
end
|
||||
|
||||
def determine_instantiator(match)
|
||||
match.captures.first == 'initialize' ? :new : :create
|
||||
end
|
||||
|
||||
def extract_attribute_names_from_match(match)
|
||||
match.captures.last.split('_and_')
|
||||
end
|
||||
|
||||
def construct_attributes_from_arguments(attribute_names, arguments)
|
||||
attributes = {}
|
||||
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
|
||||
|
|
33
activerecord/lib/active_record/dynamic_finder_match.rb
Normal file
33
activerecord/lib/active_record/dynamic_finder_match.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
module ActiveRecord
|
||||
class DynamicFinderMatch
|
||||
def self.match(method)
|
||||
df_match = self.new(method)
|
||||
df_match.finder ? df_match : nil
|
||||
end
|
||||
|
||||
def initialize(method)
|
||||
@finder = :find_initial
|
||||
case method.to_s
|
||||
when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
|
||||
@finder = :find_every if $1 == 'all_by'
|
||||
names = $2
|
||||
when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
|
||||
@instantiator = $1 == 'initialize' ? :new : :create
|
||||
names = $2
|
||||
else
|
||||
@finder = nil
|
||||
end
|
||||
@attribute_names = names && names.split('_and_')
|
||||
end
|
||||
|
||||
attr_reader :finder, :attribute_names, :instantiator
|
||||
|
||||
def finder?
|
||||
!@finder.nil? && @instantiator.nil?
|
||||
end
|
||||
|
||||
def instantiator?
|
||||
@finder == :find_initial && !@instantiator.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,6 +12,48 @@ require 'models/customer'
|
|||
require 'models/job'
|
||||
require 'models/categorization'
|
||||
|
||||
class DynamicFinderMatchTest < ActiveRecord::TestCase
|
||||
def test_find_no_match
|
||||
assert_nil ActiveRecord::DynamicFinderMatch.match("not_a_finder")
|
||||
end
|
||||
|
||||
def test_find_by
|
||||
match = ActiveRecord::DynamicFinderMatch.match("find_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert match.finder?
|
||||
assert_equal :find_initial, match.finder
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
|
||||
def test_find_all_by
|
||||
match = ActiveRecord::DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert match.finder?
|
||||
assert_equal :find_every, match.finder
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
|
||||
def test_find_or_initialize_by
|
||||
match = ActiveRecord::DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert !match.finder?
|
||||
assert match.instantiator?
|
||||
assert_equal :find_initial, match.finder
|
||||
assert_equal :new, match.instantiator
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
|
||||
def test_find_or_create_by
|
||||
match = ActiveRecord::DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert !match.finder?
|
||||
assert match.instantiator?
|
||||
assert_equal :find_initial, match.finder
|
||||
assert_equal :create, match.instantiator
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
end
|
||||
|
||||
class FinderTest < ActiveRecord::TestCase
|
||||
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
|
||||
|
||||
|
|
Loading…
Reference in a new issue