How to distinguish the same named helper methods in Rails? - ruby-on-rails-3

I have created helper function(link_to_alert) in two different helpers
app/helpers/posts_helper.rb
app/helpers/students_helper.rb
Now calling my helper function link_to_alert from app/views/student/index.html.haml
Problem is the view calling the same function present in app/helpers/posts_helper.rb
How do I call the helper function present in app/helpers/students_helper.rb from my app/views/student/index.html.haml view?

Since all helpers are included into all controllers by default the most common way is to name the functions differently, for example link_to_alert_posts and link_to_alert_students.
The second way is to disable including all helpers and pick the ones you need in a controller.
config.action_controller.include_all_helpers = false
class PostsController
helper :posts
end
class StudentsController
helper :students
end
In this case all views rendered by Posts Controller will have the function from Posts Helper, and for Students Controller - from Students Helper.
The third way is to use module_function and prefix all calls with the helper module name.
I have never seen this used particularly with Rails helpers though.
module StudentsHelper
module_function
def link_to_alert
end
end
StudentsHelper.link_to_alert
Another approach would be using presenters or decorators, but that is a totally different topic.

Why not put different names, link_to_alert_posts, link_to_alert_students?
And btw you can call it by putting the Module name and the function name.

Related

Should I use a presenter or a decorator?

I'm kinda lost with these terms since I'm a beginner in the Rails world.
I have some code in my header, not related to the model. I just want to show it or not depending on the page user is.
I know it's kinda simple and helpers would do it pretty well too, but is it possible to fit the code in a presenter or a decorator?
When do I have to use them? I really don't understand it yet.
tl;dr:
Use a Presenter in this scenario
Elaboration:
A Decorator is a structural design pattern that wraps other objects and adds new functionality without the need to extend the class you are decorating.
A Presenter on the other hand should use methods of the object you are presenting to format data in a way you want to show them. Eg. you have a User model:
class User < ActiveRecord:Base
# it has first_name and last_name columns
end
and you want to present the full name without much logic in the views. You can create a UserPresenter class like this:
class UserPresenter
def initialize(user)
#user = user
end
def full_name
"#{#user.last_name} #{#user.first_name}"
end
end
So instead of calling both attributes separately, you just do it with the presenter
user = User.new(first_name: "John", last_name: "Doe")
user_presenter = UserPresenter.new(user)
user_presenter.full_name #=> "Doe John"

Rails 3 Best Practice For Global Data

I'm currently rendering a JQuery Plugin on one of my partials in my Application layout. This partial calls one my application helper method which retrieves data generated using Sunspot-solr. The data it's retrieving is the top 30 recently created records created across various models, Uploads, Users, Articles etc. I was wondering what the best practice would be for loading this data if it appears on every view in my application?
Obviously performance is my biggest concern.
This is how I'm currently retrieving the data in my Application helper:
def get_updates
#updates = Sunspot.search(Upload,Help,User) do
order_by(:created_at, :desc)
paginate page: 1, per_page: 30
end
#updates = #updates.results
end
You could fetch that data in a before_filter in your ApplicationController. Then it would be available to any view rendered in any controller throughout the application.
pseudo code:
class ApplicationController
before_filter :load_top_30
private
def load_top_30
#top_30 = Records.find() # whatever code you need to fetch the records
end
end
Now you can use #top_30 in any view. The helper does not (and arguably should not) need to fetch the data since its already in an instance variable now, just pass it in.
If this is only this one item you wish to display globally then doing it in the ApplicationController should be fine, if you have many such elements then you might want to have a look at Cells which basically are reusable sub-controllers which you can call form your views or controllers.

Rails - include module into controller, to be used in the view

I'm really new to Rails and I try to setup a module file to be used in the view. So I believe the correct behavior is to define the module as a helper within a controller and voila, it should be working. However, that's not the case for me. Here is the structure.
lib
functions
-- form_manager.rb
form_manager.rb:
Module Functions
Module FormManager
def error_message() ...
end
end
end
users_controller.rb
class UsersController < ApplicationController
helper FormManager
def new ...
Well, the structure is like the above and when I call the error_message from new.html.erb it gives me the error: uninitialized constant UsersController::FormManager.
So, first of all, I know that in rails 3 lib is not automatically loaded. Assuming that it is not mandatory to autoload the lib folder, how can I make this work and what am I missing?
BTW, please don't say that this question is duplicate. I'm telling you I've been searching for this crap for almost 2 days.
Your module is not autoloaded (at least not in 3.2.6). You have to load it explicitly. You can achieve this with the following line of code
# in application.rb
config.autoload_paths += %W(#{config.root}/lib)
You can check your autoload paths with Rails.application.config.autoload_paths. Maybe it's indeed defined for you?
Now you're sure your module gets loaded, you can check it in rails console by calling
> Functions::FormHelper
Now you can't use that module as a view helper by default. Use #included to define the helper when your module gets included. You achieve "lazy evaluation" this way. I think the problem with your code is that the helper method gets called before the module gets included. (somebody should correct me if I'm wrong)
Here's the code:
Module Functions
Module FormManager
def error_message() ...
end
def self.included m
return unless m < ActionController::Base
m.helper_method :error_message
end
end
end
You should also remove the helper line from your controller.
EDIT:
You can achieve this without autoloading. Just use require "functions/form_manager". You define a helper_method for every method. If you wish use all the module methods as helpers use
def self.included m
return unless m < ActionController::Base
m.helper_method self.instance_methods
end
EDIT2:
It appears that you don't need to use self.included. This achieves the same functionality:
class ApplicationController < ActionController::Base
include Functions::FormManager
helper_method Functions::FormManager.instance_methods
end
It appears you are namespacing FormManager inside of Functions which means you would call it by helper Functions::FormManager
Try that

Rail3 | How to create standard route/action for ALL controllers?

Well, DRY! So i thought it should be easy to add a new action (like the existing new, edit) to all my controllers (in my case copy). But how do you setup a new route for ALL controllers?
Without going in to 'loops' (i.e. %w().each ...) inside the routes.rb ?
I mean, we want DRY right? So you don't want copy your action inside the routes file for each resource. I guess you should be able to extend the default actions/routes (index, new, edit,etc.) easy?
Thanks!
AFIK no way to do this by default. You could monkey-patch resources to include this functionality:
https://github.com/rails/rails/blob/b229bc70e50ec0887c5bb3aaaa9c6ee8af054026/actionpack/lib/action_dispatch/routing/mapper.rb#L982
...but my hunch is you would be better off re-considering whether this functionality can be created another way, since what you want to do is "off the Rails".
One option is create a CloneController#new that accepts a model and id and creates a clone. This seems like it would be drier, and wouldn't require you to pepper a gazillion "clone_article" "clone_blog" "clone_user" paths all over the place.
Obviously you would want to carefully white-list the models/ids that can be passed in.
Looking through the source there isn't a way to add to the default actions for a resource.
But, as #juwiley says, the methods resources :item is just a shortcut for creating a load of member and collection methods.
All you need to do is something like this
class ActionDispatch::Routing::Mapper
def resources_with_copy(*resources, &block)
block_with_copy = lambda do
block.call
member do
post :copy
end
end
resources(*resources, &block_with_copy)
end
end
Then in your routes.rb just say
resources_with_copy :items
resources_with_copy :posts do
member do
post :share
end
end
...

What is the best way to translate slugs in Rails routes

I'm trying to achieve full internationalization of my routes in a Rails3.1 app. I'm already using Francesc Pla's rails-translate-routes to localize route actions and resources. The last step is to be able to translate slugs for some of my models.
Route to be translated:
http://myhost.com/products/pharmacy --> http://myhost.com/productos/farmacia
I have a nested route of the form
# routes.rb
match 'products/:category_slug' => "products#index"
I have a model Category with an instance #<Category id: 1, slug: "pharmacy"> and I do find_by_slug category in my ProductsController.
Any ideas on how to do translate the :category_slug part of the route?
As far as I'm aware, you can call translation helpers directly from your controller as long as you namespace correctly with I18n.
So your ProductsController could contain something like the following:
class ProductsController < ApplicationController
def index
i18n_slug = I18n.t("locale.category.#{params[:category_slug]}")
#category = Category.find_by_slug(i18n_slug)
end
end
You should probably inform yourself as to potential security risks of passing the params directly into the translation engine, though I'm not aware of any. You might also consider
moving that into a before filter or into the application controller if it will be used in multiple controller actions.