Add Relation extensions

This commit is contained in:
Pratik Naik
2010-04-02 17:34:48 +01:00
parent bc7da9b77d
commit b77dd218ce
7 changed files with 68 additions and 15 deletions

View File

@@ -1,5 +1,15 @@
*Rails 3.0.0 [Beta 2] (pending)*
* Add Relation extensions. [Pratik Naik]
users = User.where(:admin => true).extending(User::AdminPowers)
latest_users = User.order('created_at DESC') do
def posts_count
Post.count(:user_id => to_a.map(&:id))
end
end
* To prefix the table names of all models in a module, define self.table_name_prefix on the module. #4032 [Andrew White]
* Silenced "SHOW FIELDS" and "SET SQL_AUTO_IS_NULL=0" statements from the MySQL driver to improve log signal to noise ration in development [DHH]

View File

@@ -133,19 +133,16 @@ module ActiveRecord
delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass
def self.init(klass, options, &block)
relation = new(klass, klass.arel_table)
relation = new(klass, klass.arel_table, &block)
scope = if options.is_a?(Hash)
klass.scoped.apply_finder_options(options.except(:extend))
klass.scoped.apply_finder_options(options)
else
options ? klass.scoped.merge(options) : klass.scoped
end
relation = relation.merge(scope)
Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash)
relation.send(:extend, Module.new(&block)) if block_given?
relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
relation
end

View File

@@ -4,7 +4,7 @@ module ActiveRecord
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :extends]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
@@ -13,8 +13,9 @@ module ActiveRecord
delegate :insert, :to => :arel
attr_reader :table, :klass
attr_accessor :extensions
def initialize(klass, table)
def initialize(klass, table, &block)
@klass, @table = klass, table
@implicit_readonly = nil
@@ -22,6 +23,9 @@ module ActiveRecord
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@extensions = []
apply_modules(Module.new(&block)) if block_given?
end
def new(*args, &block)

View File

@@ -10,8 +10,9 @@ module ActiveRecord
next if [:where, :having].include?(query_method)
class_eval <<-CEVAL
def #{query_method}(*args)
def #{query_method}(*args, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?
value = Array.wrap(args.flatten).reject {|x| x.blank? }
new_relation.#{query_method}_values += value if value.present?
new_relation
@@ -21,8 +22,9 @@ module ActiveRecord
[:where, :having].each do |query_method|
class_eval <<-CEVAL
def #{query_method}(*args)
def #{query_method}(*args, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?
value = build_where(*args)
new_relation.#{query_method}_values += [*value] if value.present?
new_relation
@@ -34,8 +36,9 @@ module ActiveRecord
attr_accessor :"#{query_method}_value"
class_eval <<-CEVAL
def #{query_method}(value = true)
def #{query_method}(value = true, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?
new_relation.#{query_method}_value = value
new_relation
end
@@ -43,8 +46,16 @@ module ActiveRecord
end
end
def lock(locks = true)
def extending(*modules)
new_relation = clone
new_relation.send :apply_modules, *modules
new_relation
end
def lock(locks = true, &block)
relation = clone
relation.send(:apply_modules, Module.new(&block)) if block_given?
case locks
when String, TrueClass, NilClass
clone.tap {|new_relation| new_relation.lock_value = locks || true }
@@ -191,6 +202,12 @@ module ActiveRecord
private
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
values.each {|extension| extend(extension) }
end
def reverse_sql_order(order_query)
order_query.to_s.split(/,/).each { |s|
if s.match(/\s(asc|ASC)$/)

View File

@@ -7,9 +7,8 @@ module ActiveRecord
return merged_relation unless r
(Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
unless (value = r.send(:"#{method}_values")).blank?
merged_relation.send(:"#{method}_values=", value)
end
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", value) if value.present?
end
merged_relation = merged_relation.joins(r.joins_values)
@@ -34,6 +33,9 @@ module ActiveRecord
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
# Apply scope extension modules
merged_relation.send :apply_modules, r.extensions
merged_relation
end
@@ -69,7 +71,7 @@ module ActiveRecord
result
end
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
:order, :select, :readonly, :group, :having, :from, :lock ]
def apply_finder_options(options)
@@ -84,6 +86,7 @@ module ActiveRecord
relation = relation.where(options[:conditions]) if options.has_key?(:conditions)
relation = relation.includes(options[:include]) if options.has_key?(:include)
relation = relation.extending(options[:extend]) if options.has_key?(:extend)
relation
end

View File

@@ -572,4 +572,20 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.all, all_posts.all
end
def test_anonymous_extension
relation = Post.where(:author_id => 1).order('id ASC') do
def author
'lifo'
end
end
assert_equal "lifo", relation.author
assert_equal "lifo", relation.limit(1).author
end
def test_named_extension
relation = Post.where(:author_id => 1).order('id ASC').extending(Post::NamedExtension)
assert_equal "lifo", relation.author
assert_equal "lifo", relation.limit(1).author
end
end

View File

@@ -1,4 +1,10 @@
class Post < ActiveRecord::Base
module NamedExtension
def author
'lifo'
end
end
scope :containing_the_letter_a, where("body LIKE '%a%'")
scope :ranked_by_comments, order("comments_count DESC")
scope :limit_by, lambda {|l| limit(l) }