Search code examples
ruby-on-railsrubymoduleactioncontrollerclass-variables

class variables and module inclusion, specifically in ActionController


I want to have some kind of single list that is initialized in a seperate module, then can be included in a controller and modified at the controller-class level, and accessed at the controller-instance level. I thought class variables would work here, but something strange is happening, they don't seem to be being initialized within my ending class.

More specifically:

I have many controllers all including some default functionality, in a module.

class BlahController < ApplicationController
  include DefaultFunctionality
end

class FooController < ApplicationController
  include DefaultFunctionality
end

 module DefaultFunctionality 
   def show 
     render 'shared/show'
   end 
   def model
     controller_name
   end
 end

, for instance. This isn't the actual code, but that's the most interaction it has at the moment.

I'd like to extend this with some other functionality (a sortable interface for lists,) like so [note I'd like to be able to swap out the sort-order-list functionality on a class by class basis]:

module DefaultFunctionality
 module Sortable
  def sort_params
    params.slice(:order, :sort_direction).reverse_merge(default_sort_params)
  end
  def default_sort_params
    @@sorts.first
  end
  def set_sorts(sorts = []) #sorts = [{:order => "most_recent", :sort_direction => :desc},...]
     @@sorts = sorts
  end
 end
 include Sortable
 set_sorts([{:order => :alphabetical, :sort_direction => :asc}] #never run?
end

The idea is to make sure that I'm able to swap out the set of all possible sorts on a class by class basis, like so:

class FooController < ApplicationController
  include DefaultFunctionality #calls the default set_sorts
  set_sorts([{:order => :most_recent, :sort_direction => :asc}]) 
end

And also to make nice links in the views, like below, except that I'm getting an error.

___/blah/1 => shared/show.html.erb__
<%= link_to("upside down", polymorphic_path(model, sort_params) %><%#BOOOM uninitialized class variable @@sorts for BlahController %>

I figure the class_var is a bad call, but I can't think of what else I might use. (a class instance var?)


Solution

  • I ended up using a class level instance variable and included() with no autoincluded submodules, before noticing that @tadman had responded.

    It ended up looking like this:

    class BlahController < ApplicationController
      include DefaultControllerFunctionality
      include DefaultControllerFunctionality::Sortable
    end
    
    class FooController < ApplicationController
      include DefaultControllerFunctionality
      include DefaultControllerFunctionality::Sortable
      sorts=([{:order => :most_recent, :sort_direction => :desc}])
    end
    

    in the controllers, and under /lib

    lib/default_controller_functionality.rb

     module DefaultFunctionality 
       def show 
         render 'shared/show'
       end 
       def model
         controller_name
       end
     end
    

    lib/default_controller_functionality/sortable.rb

     module DefaultControllerFunctionality
       module Sortable
        def self.included(base)
          base.helper_method :sort_params
          base.class_eval <<-END
              @sorts=[]
              class << self; attr_accessor :sorts end
           END
           #this line ^ makes it so that the module 
           #doesn't work when included in any other 
           #module than the class you want @sorts to end up in. 
           #So don't include(Sortable) in DefaultControllerFunctionality and expect .sorts to work in FooController.
    
          base.sorts= [{:order => :alphabetical, :sort_direction => :asc}]
        end
        def sort_params
          params.slice(:order, :sort_direction).reverse_merge(default_sort_params)
        end
        def default_sort_params
          self.class.sorts.first
        end
       end
     end