ActiveScaffold reverse associations to models in modules

I really enjoy using ActiveScaffold, and encourage everyone to use it to generate administration style interfaces. I came across an apparent defect today that I wanted to share.

I have a set of models that are contained in a module, and I’m using AS to do administration for them. Mostly fine. However there’s a problem when I click a link on an element of a many relationship that’s being displayed in a list. In this image,

Metalicus

ranges is the result of a has_many relationship, and when I click on the range ‘Summer Selection’, I get this:

Metalicus

and the following stacktrace:

ActionView::TemplateError (undefined method `reflect_on_all_associations' for Range:Class) on line #19 of vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:
16:       # determine what constraints we need
17:       if column.through_association?
18:         @constraints = {
19:           association.source_reflection.reverse => {
20:             association.through_reflection.reverse => parent_id
21:           }
22:         }

    vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:26:in `reverse_matches_for'
    vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:12:in `reverse'
    vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:19:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
    vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `each'
    vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
    vendor/rails/actionpack/lib/action_view/base.rb:637:in `send'
    vendor/rails/actionpack/lib/action_view/base.rb:637:in `compile_and_render_template'
    vendor/rails/actionpack/lib/action_view/base.rb:365:in `render_template'
    vendor/rails/actionpack/lib/action_view/base.rb:316:in `render_file'
    vendor/rails/actionpack/lib/action_view/base.rb:331:in `render_without_active_scaffold'

The problem is that class should be MyModule::Range, not Range.

AS is looking up the class using code on ActiveRecord::Reflection::AssociationReflection, from the file activescaffold/lib/extensions/reverse_associations.rb. The fix (apparently – I haven’t tested this exhaustively yet) is to change the code that sends “class_name.constantize” to the association to send “klass” instead (you need to do this in two places). The completed code is attached below. I hope this helps someone else!

	module ActiveRecord
	  module Reflection
	    class AssociationReflection #:nodoc:
	      def reverse_for?(klass)
	        reverse_matches_for(klass).empty? ? false : true
	      end

	      attr_writer :reverse
	      def reverse
	        unless @reverse
	          # Following line changed for compatibility with associations on classes in modules
	          # reverse_matches = reverse_matches_for(self.class_name.constantize)
	          reverse_matches = reverse_matches_for(self.klass)
	          # grab first association, or make a wild guess
	          @reverse = reverse_matches.empty? ? self.active_record.to_s.pluralize.underscore : reverse_matches.first.name
	        end
	        @reverse
	      end

	      protected

	        def reverse_matches_for(klass)
	          reverse_matches = []

	          # stage 1 filter: collect associations that point back to this model and use the same primary_key_name
	          klass.reflect_on_all_associations.each do |assoc|
	            # skip over has_many :through associations
	            next if assoc.options[:through]

	            ## Following line changed for compatibility with associations on classes in modules
	            # next unless assoc.options[:polymorphic] or assoc.class_name.constantize == self.active_record
	            next unless assoc.options[:polymorphic] or assoc.klass == self.active_record
	            case [assoc.macro, self.macro].find_all{|m| m == :has_and_belongs_to_many}.length
	              # if both are a habtm, then match them based on the join table
	              when 2
	              next unless assoc.options[:join_table] == self.options[:join_table]

	              # if only one is a habtm, they do not match
	              when 1
	              next

	              # otherwise, match them based on the primary_key_name
	              when 0
	              next unless assoc.primary_key_name.to_sym == self.primary_key_name.to_sym
	            end

	            reverse_matches < 1

	          reverse_matches
	        end

	    end
	  end
	end

~ by Steve Hayes on January 31, 2008.

One Response to “ActiveScaffold reverse associations to models in modules”

  1. Helps me! I spent a half day on this problem! Thanks so much!

Leave a Reply