class variables and module inclusion, specifically in ActionController
I want to have some kind of single list that is initialized in a se开发者_高级运维perate 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?)
Class instance vars are the way to go, for sure. Rarely do you actually need to use a class variable.
In specific answer to your problem, keep in mind that any code defined in your module is executed only once when the module is loaded not when the module is included. This distinction may not be obvious, especially when people consider "include" to be equivalent to "require".
What you need is this:
module DefaultFunctionality
def sort_params
params.slice(:order, :sort_direction).reverse_merge(default_sort_params)
end
def default_sort_params
@sorts.first
end
def sorts=(sorts = nil)
@sorts = sorts || [{:order => "most_recent", :sort_direction => :desc}]
end
def self.included(base_class)
self.sorts = ([{:order => :alphabetical, :sort_direction => :asc}]
end
end
The way you catch a class including your module is to define Module.included accordingly. In this case, set_sorts is called each time this module is included, and it is in the context of the calling class.
I've modified this a bit to include a few specific style changes:
- Declaring your defaults inside the method instead of in the declaration. Avoids producing unreadably long lines, or having to one-line otherwise complicated data structures.
- Using class instance variables which will do the job in this case.
- Using a Ruby-style var= mutator method instead of set_var()
You have to add a included
method to your module for this to work.
module DefaultFunctionality
def self.included(base)
base.include(Sortable)
set_sorts([{:order => :alphabetical, :sort_direction => :asc}] #never run?
end
module Sortable
# other methods
def set_sorts(sorts = [{:order => "most_recent", :sort_direction => :desc}])
@@sorts = sorts
end
end
end
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
精彩评论