diff --git a/lib/grouped_scope.rb b/lib/grouped_scope.rb index 8471f34..8523513 100644 --- a/lib/grouped_scope.rb +++ b/lib/grouped_scope.rb @@ -1,6 +1,7 @@ require 'grouped_scope/errors' require 'grouped_scope/grouping' require 'grouped_scope/self_grouping' +require 'grouped_scope/association_reflection' require 'grouped_scope/class_methods' require 'grouped_scope/instance_methods' require 'grouped_scope/has_many_association' diff --git a/lib/grouped_scope/association_reflection.rb b/lib/grouped_scope/association_reflection.rb new file mode 100644 index 0000000..58fa449 --- /dev/null +++ b/lib/grouped_scope/association_reflection.rb @@ -0,0 +1,43 @@ +module GroupedScope + class AssociationReflection < ActiveRecord::Reflection::AssociationReflection + + (ActiveRecord::Reflection::MacroReflection.instance_methods + + ActiveRecord::Reflection::AssociationReflection.instance_methods).uniq.each do |m| + unless m =~ /^(__|nil\?|send)|^(object_id|options|name)$/ + delegate m, :to => :ungrouped_reflection + end + end + + def initialize(active_record,ungrouped_name) + @active_record = active_record + @ungrouped_name = ungrouped_name + @name = :"grouped_scope_#{@ungrouped_name}" + verify_ungrouped_reflection + super(ungrouped_reflection.macro, @name, ungrouped_reflection.options.dup, @active_record) + create_grouped_association + end + + def ungrouped_reflection + @active_record.reflections[@ungrouped_name] + end + + + private + + def verify_ungrouped_reflection + if ungrouped_reflection.blank? || ungrouped_reflection.macro.to_s !~ /has_many|has_and_belongs_to_many/ + raise ArgumentError, "Cannot create a group scope for :#{@ungrouped_name} because it is not a has_many " + + "or a has_and_belongs_to_many association. Make sure to call grouped_scope after " + + "the has_many associations." + end + end + + def create_grouped_association + active_record.send :has_many, name, options + active_record.reflections[name] = self + active_record.grouped_scopes[@ungrouped_name] = true + options[:grouped_scope] = true + end + + end +end \ No newline at end of file diff --git a/lib/grouped_scope/class_methods.rb b/lib/grouped_scope/class_methods.rb index 134cfcf..c753205 100644 --- a/lib/grouped_scope/class_methods.rb +++ b/lib/grouped_scope/class_methods.rb @@ -5,32 +5,17 @@ def grouped_scopes read_inheritable_attribute(:grouped_scopes) || write_inheritable_attribute(:grouped_scopes, {}) end - def grouped_scope(*args) - create_belongs_to_grouped_scope_grouping - args.each do |association| - existing_assoc = reflect_on_association(association) - unless existing_assoc && existing_assoc.macro == :has_many - raise ArgumentError, "Cannot create a group scope for :#{association} because it is not a has_many " + - "association. Make sure to call grouped_scope after the has_many associations." - end - grouped_scopes[association] = true - grouped_assoc = grouped_scope_for(association) - has_many grouped_assoc, existing_assoc.options.dup - reflect_on_association(grouped_assoc).options[:grouped_scope] = true - end + def grouped_scope(*associations) + create_belongs_to_for_grouped_scope + associations.each { |association| AssociationReflection.new(self,association) } include InstanceMethods end - def grouped_scope_for(association) - :"grouped_scope_#{association}" - end - - private - def create_belongs_to_grouped_scope_grouping + def create_belongs_to_for_grouped_scope grouping_class_name = 'GroupedScope::Grouping' - existing_grouping = reflect_on_association(:grouping) + existing_grouping = reflections[:grouping] return false if existing_grouping && existing_grouping.macro == :belongs_to && existing_grouping.options[:class_name] == grouping_class_name belongs_to :grouping, :foreign_key => 'group_id', :class_name => grouping_class_name end diff --git a/lib/grouped_scope/self_grouping.rb b/lib/grouped_scope/self_grouping.rb index 390f0c9..303b478 100644 --- a/lib/grouped_scope/self_grouping.rb +++ b/lib/grouped_scope/self_grouping.rb @@ -67,8 +67,7 @@ def proxy_class def method_missing(method, *args, &block) if proxy_class.grouped_scopes[method] - grouped_assoc = proxy_owner.class.grouped_scope_for(method) - proxy_owner.send(grouped_assoc, *args, &block) + proxy_owner.send("grouped_scope_#{method}", *args, &block) else super end diff --git a/test/grouped_scope/association_reflection_test.rb b/test/grouped_scope/association_reflection_test.rb new file mode 100644 index 0000000..d7c8cb5 --- /dev/null +++ b/test/grouped_scope/association_reflection_test.rb @@ -0,0 +1,49 @@ +require File.dirname(__FILE__) + '/../helper' + +class AssociationReflectionTest < GroupedScope::TestCase + + def setup + setup_environment + end + + context 'Raise and exception' do + + setup { @reflection_klass = GroupedScope::AssociationReflection } + + should 'when a association does not exist' do + assert_raise(ArgumentError) { @reflection_klass.new(Employee,:foobars) } + end + + should 'when the association is not a has_many or a has_and_belongs_to_many' do + Employee.class_eval { belongs_to(:foo) } + assert_raise(ArgumentError) { @reflection_klass.new(Employee,:foo) } + end + + end + + context 'For #ungrouped_reflection' do + + should 'access ungrouped reflection' do + assert_equal Employee.reflections[:reports], + Employee.reflections[:grouped_scope_reports].ungrouped_reflection + end + + should 'delegate core instance methods to #ungrouped_reflection' do + [:class_name,:klass,:table_name,:quoted_table_name,:primary_key_name, + :association_foreign_key,:counter_cache_column,:source_reflection].each do |m| + assert_equal Employee.reflections[:reports].send(m), + Employee.reflections[:grouped_scope_reports].send(m), + "The method #{m.inspect} does not appear to be proxed to the ungrouped reflection." + end + end + + should 'not delegate to #ungrouped_reflection for #options and #name' do + assert_not_equal Employee.reflections[:reports].name, Employee.reflections[:grouped_scope_reports].name + assert_not_equal Employee.reflections[:reports].options, Employee.reflections[:grouped_scope_reports].options + end + + end + + + +end diff --git a/test/grouped_scope/class_methods_test.rb b/test/grouped_scope/class_methods_test.rb index a5c3012..ae9611b 100644 --- a/test/grouped_scope/class_methods_test.rb +++ b/test/grouped_scope/class_methods_test.rb @@ -23,11 +23,7 @@ def setup context 'For .grouped_scope' do should 'create a belongs_to :grouping association' do - assert Employee.reflect_on_association(:grouping) - end - - should 'raise an exception when has_many association does not exist' do - assert_raise(ArgumentError) { Employee.class_eval{grouped_scope(:foobars)} } + assert Employee.reflections[:grouping] end should 'not recreate belongs_to :grouping on additional calls' do @@ -36,21 +32,17 @@ def setup end should 'create a has_many assoc named :grouped_scope_* using existing association as a suffix' do - grouped_reports_assoc = Employee.reflect_on_association(:grouped_scope_reports) + grouped_reports_assoc = Employee.reflections[:grouped_scope_reports] assert_instance_of ActiveRecord::Reflection::AssociationReflection, grouped_reports_assoc + assert Factory(:employee).respond_to?(:grouped_scope_reports) end should 'not add the :grouped_scope option to existing reflection' do - assert_nil Employee.reflect_on_association(:reports).options[:grouped_scope] + assert_nil Employee.reflections[:reports].options[:grouped_scope] end - should 'mirror existing options for has_many association, minus :grouped_scope option' do - reports_assoc = Employee.reflect_on_association(:reports) - grouped_reports_assoc = Employee.reflect_on_association(:grouped_scope_reports) - assert_equal({:grouped_scope=>true}, grouped_reports_assoc.options.diff(reports_assoc.options)) - reports_assoc = LegacyEmployee.reflect_on_association(:reports) - grouped_reports_assoc = LegacyEmployee.reflect_on_association(:grouped_scope_reports) - assert_equal({:grouped_scope=>true}, grouped_reports_assoc.options.diff(reports_assoc.options)) + should 'have added the :grouped_scope option to new grouped reflection' do + assert Employee.reflections[:grouped_scope_reports].options[:grouped_scope] end end