mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Introduce dynamic scopes for ActiveRecord: you can now use class methods like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that will use the scoped method with attributes you supply. [#1648 state:committed]
Signed-off-by: David Heinemeier Hansson <david@loudthinking.com>
This commit is contained in:
committed by
David Heinemeier Hansson
parent
1fb275541a
commit
66ee5890c5
@@ -1,5 +1,7 @@
|
||||
*2.3.0/3.0*
|
||||
|
||||
* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
|
||||
|
||||
* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
|
||||
|
||||
* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
|
||||
|
||||
@@ -51,6 +51,7 @@ module ActiveRecord
|
||||
autoload :Callbacks, 'active_record/callbacks'
|
||||
autoload :Dirty, 'active_record/dirty'
|
||||
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
|
||||
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
|
||||
autoload :Migration, 'active_record/migration'
|
||||
autoload :Migrator, 'active_record/migration'
|
||||
autoload :NamedScope, 'active_record/named_scope'
|
||||
|
||||
@@ -1456,7 +1456,10 @@ module ActiveRecord #:nodoc:
|
||||
def respond_to?(method_id, include_private = false)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
@@ -1809,7 +1812,11 @@ module ActiveRecord #:nodoc:
|
||||
# This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
|
||||
# or find_or_create_by_user_and_password(user, password).
|
||||
#
|
||||
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
|
||||
# are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively.
|
||||
#
|
||||
# Each dynamic finder, scope 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, &block)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
@@ -1868,6 +1875,22 @@ module ActiveRecord #:nodoc:
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments, &block)
|
||||
end
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
if match.scope?
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
|
||||
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
|
||||
) # )
|
||||
#
|
||||
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
||||
end # end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
25
activerecord/lib/active_record/dynamic_scope_match.rb
Normal file
25
activerecord/lib/active_record/dynamic_scope_match.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module ActiveRecord
|
||||
class DynamicScopeMatch
|
||||
def self.match(method)
|
||||
ds_match = self.new(method)
|
||||
ds_match.scope ? ds_match : nil
|
||||
end
|
||||
|
||||
def initialize(method)
|
||||
@scope = true
|
||||
case method.to_s
|
||||
when /^scoped_by_([_a-zA-Z]\w*)$/
|
||||
names = $1
|
||||
else
|
||||
@scope = nil
|
||||
end
|
||||
@attribute_names = names && names.split('_and_')
|
||||
end
|
||||
|
||||
attr_reader :scope, :attribute_names
|
||||
|
||||
def scope?
|
||||
!@scope.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -278,3 +278,23 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeMatchTest < ActiveRecord::TestCase
|
||||
def test_scoped_by_no_match
|
||||
assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
|
||||
end
|
||||
|
||||
def test_scoped_by
|
||||
match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert match.scope?
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeTest < ActiveRecord::TestCase
|
||||
def test_dynamic_scope
|
||||
assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
|
||||
assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user