Rails sharing controller functionality and menus - ruby-on-rails-3.1

Sorry if this seems daft (not much sleep lately) but I'm trying to figure out the best way to share controller (and method) functionality between controllers without writing the same code for each one.
Let's say I have various controllers with the same method called search. Inside this method I have situations for the same term for different controllers, like:
case term
when "beginning_parens"
#books = Book.where("title REGEXP ? AND localeLanguage LIKE ?", '^\\([0-9]*\\)', params[:language]).not_locked.reorder(order).paginate(:page => params[:page])
when 'search_all_caps'
#books = Book.detect_uppercase.not_locked.reorder(order).where("localeLanguage LIKE ?", params[:language]).paginate(:page => params[:page])
Now I don't want to have to write all of that for each controller. And I'd like not to have to pass the params[:language], paginate, etc. to each of the cases since I want each one to search by language and paginate as well.
Or course, the instance variable would change among controllers, instead of #books we would have #dvds, #games, etc.
I also find myself having to pass all the params in the menu links:
= link_to 'with all CAPS', books_search_path(:term => 'search_all_caps', :language => params[:language])
And I'm sure there has to be a better way to do this.
I'm already sharing common methods for the models with a file I call at the top of each model with extend ModelFunctions. This file contains methods such as detect_uppercase which is used above and is common for many controllers. So at least I have that part working :)
Any help would be appreciated.

If you're going to be writing a bunch of controllers that do just about the same thing, it seems like inheritance might be the way to go here.
Make one controller (let's call it the BaseController) and give it all the methods you want the other controllers to have. Then each of your other controllers can inherit from BaseController and just override the methods you need them to and set class variables for things like which model to search, what instance variable to set, and so on. For example:
class BaseController < ActionController::Base
def search
##model.where("title REGEXP ? AND localelanguage ...")
end
end
class BooksController < BaseController
##model = Book
end

Related

Ruby, Sinatra views and class methods

I'm learning how to use Sinatra. I figured out that when I pass object as locals, e.g.:
product = FetchProduct.new.call(id) #function finds exact Product instance
erb :"products/show", locals: { product: product }
I can use product object in my views with all instance methods I declared. But I can't use any class method, any attempt to do so gives me uninitialized constant error. What should I do if I want to use Product.format_price(product.price) method? Is there any way to pass class methods to Sinatra views?
klass = const_get( product.class )
klass.format_price
But that doesn't really make sense because you already know you want Product.format_price. So why don't you use Product.format_price?
It's generally a bad idea to run that kind of logic in your views. Best practice is to, wherever possible, serve to the view whatever it needs.
NB the reason you can't run the class method in your view is because Product is not accessible in your view and, to be honest, it shouldn't be if you want to follow MVC principles.
If it's just the format_price method you need in the view (especially since you seem to be passing an instance of Product into Product.format_price which is rather strange and a big code smell), then either create a helper method called format_price that is accessible by the view or, probably better, create a helper method called format_price in your controller (or in a helper module included in your controller) and pass the return value as a local i.e.
get '/' do
product = FetchProduct.new.call(id)
erb :'products/show', locals: {
product: product,
price: format_price(product)
}
end
private
def format_price(product)
# awesome formatting logic
end

Rails Metaprograming/Refactoring/DRYing the controller

I have a controller with several actions. Many follow this pattern:
def favorites
#favorites = Favorite.where(organization_id: #resource.id).page(params[:page]).per(50)
end
It's not just favorites, but there's also downloads, searches, lists etc and they're all so similar that I wanted to create a method that I could call in a before_filter. Something like this:
def set_instance_variable
subject = __method__
class = __method__.singularize.constantize
instance_variable = self.class.instance_variable_set("##{subject}", "#{class}.where(organization_id: #resource.id).page(params[:page]).per(50)")
end
The syntax might be a little off here, but I know this won't work because __method__ will always be set_instance_variable and not the parent method where it is called.
Is there a way to dynamically set instance variables based on the method that defines them? Is this example above even on the right track?
I like the way the CanCan library handles this problem. With CanCan, you call a class method at the top of your controller:
load_resource
CanCan then looks at:
the action you're in to determine whether you want a collection or singular resource,
the name of the controller to determine the class to load
authorization rules to add scopes like your organization_id restriction (cancan is also a library for defining these)
I think pagination and resource loading are separate things, and you shouldn't put them in the same method. I'd shoot for an interface like this:
class FavoritesController
load_resource
paginate_resource only: [:index]
def show
# #favorite loaded here
end
def index
# #favorites loaded and paginated here
end
end
https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/controller_resource.rb#L29
If it makes more sense in your application to have non-restful resources, then you can't re-use the convention-based thing cancan is and instead have to define your own function. Consider something like this:
def favorites
#favorites = load_resource Favorite
end
private
def load_resource(klass)
klass.where(organization_id: #resource.id).page(params[:page]).per(50)
end

Overriding AR order scope

I have a Project model and a Developer model. I have the concept of calculating the "interestingness" for a project for a particular developer:
class Project < ActiveRecord::Base
def interestingness_for(developer)
some_integer_based_on_some_calculations
end
end
I think it would be neat, instead of having something like Project.order_by_interestingness_for(bill), to be able to say
Project.order(:interestingness, :developer => bill)
and have it be a scope as opposed to just a function, so I can do stuff like
Project.order(:interestingness, :developer => bill).limit(10)
I don't know how to do this, though, because it's not obvious to me how to override a scope. Any advice?
Assuming you will not need to use the standard ActiveRecord order query method for the Project class, you can override it like any other class method:
def self.order(type, options)
self.send(:"special_#{type}_calculation_via_scopes", options)
end
Then the trick is to ensure you create the needed calculation methods (which will vary according to your interestingness and other algorithms). And that the calculation methods only use scopes or other AR query interface methods. If you aren't comfortable converting the method logic to a SQL equivalent using the query interface, you can try using the Squeel DSL gem which can potentially work with the method directly depending on your specific calculation.
If you may be needing the classic order method (and this is usually a safe assumption), then don't override it. Either create a proxy non-ActiveRecord object for this purpose, or use a different naming convention.
If you really want to, you can use aliasing to achieve a similar effect, but it may have unintended consequences for the long term if the second argument ('options' in this case) suddenly takes on another meaning as Rails progresses. Here is an example of what you can use:
def self.order_with_options(type, options = nil)
if options.nil?
order_without_options(type)
else
self.send(:"special_#{type}_calculation_via_scopes", options)
end
end
class << self
alias_method_chain :order, :options
end

How to retrieve the controller class from within a view/helper in Rails 3?

I know about controller_name returning a string containing the controller's name but how can I retrieve the controller class (or object) from within a helper?
EDIT: The solution should also work when the controller is namespaced (eg. Admin::PostsController)
You can use the constantize method, like:
controller_name.constantize
Though I'm not sure how it will behave if you have a namespaced controller.
Update:
That one won't work for all controller names and/or namespaces. Though one can use the #controller method in combination with #class:
controller.class
A view probably shouldn't need to do this. Ideally whatever you're trying to do in the view that expects this, you would instead do in the controller.
Trying to think about why you'd want to do this, the best answer I can think of is that you want to invoke a helper method you've defined in the controller. There already exists a construct to do this, use helper_method.
For pretty much anything else, the controller should provide that data to the view. Not the view pulling it out of the controller. (e.g. even though you shouldn't need the class, the controller could provide it with #controller_class = self.class, which would then be available to the view)
In pure Ruby, because class names are constants, you can do this to get the class from a string:
classname = 'Posts'
p Kernel.const_get(classname).methods
There is a nice shortcut in Rails, constantize for just this:
p 'Posts'.constantize.methods
If the classname is eg 'editable_file', first call the camelize method:
p 'editable_file'.camelize.constantize # EditableFile
p 'extensions/editable_file'.camelize.constantize # Extensions::EditableFile
EDIT: If you really want to get the controller name un-demodulized, then this code in config/initializers/controller_name.rb should ensure it:
class ActionController::Metal
def self.controller_name
# #controller_name ||= self.name.demodulize.sub(/Controller$/, '').underscore
#controller_name ||= self.name.sub(/Controller$/, '').underscore
end
end

How to access Warden current user in business logic in Rails3

I am using Rails Warden plugin. It defines a helper method 'user' that returns current user. See the link for the source code.
Now I have an business logic object that does not have any reference to the controller. But I would like to get the current user. Is there any way of accessing this?
I have tried
ActionController::Base.helpers.user
or even
RailsWarden::Mixins::HelperMethods.user
with no luck. Thanks.
Now I have an business logic object
that does not have any reference to
the controller. But I would like to
get the current user. Is there any way
of accessing this?
So why can't you just pass the current user to those methods?
Additionally you can mix them in.
I strongly discourage you to write the static helpers (it is not Java, it is Ruby!).
Instead, where you need those helpers include them as a module:
module SuperLogic
def calculate_stuff(current_user=nil)
(current_user || user || self).bills.sum
end
edn
Then include this where you need it:
# user model
class User
include SuperLogic
#it will get the `calculate_stuff` method
end
# controller
include SuperLogic
# so you can use it as one of
calculate_stuff user
calculate_stuff
and so on...
additionally where you access your business logic, you can just create an instance of the class instead of "static" methods (in ruby they are "class" methods):
# controller
def calculate
#result = BusinessLogic.new(user).calculate_stuff
end
This is probably the easiest thing you can do.
Really, you don't need to access whole HTTP context in your business objects (I'm not even talking about testing it).
The way I think of business logic, it's something that sits between the controller and the model. I think it would be ok to pass an instance of the request to the logic methods, and since you're using warden, you can get the user from 'request.env['warden'].user'.
I haven't encountered a good reason not to have logic methods be static (self.) methods of a module. Maybe Dmytrii's suggestion works for you, but I prefer to 'require' than to dynamically include one-off logic bits.

Resources