1
0
Fork 0
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:
Josh Susser 2008-08-25 19:32:19 -07:00 committed by Jeremy Kemper
parent 3beed9cdb7
commit 143f5fbb21
4 changed files with 125 additions and 72 deletions

View file

@ -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

View file

@ -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] }

View 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

View file

@ -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