Rails, hooking into controllers - ruby-on-rails

OK, here's my problem:
I have a HomeController with an action, say index, that would render a landing page for my site. The site also has a bunch of other Controllers that implement actions for various other functionalities.
I want a dynamic navigation bar on my home page and what I'm looking for is that HomeController#index goes to every other Controller in my app and calls a particular method that would return and object with link details, for example, HomeController#index would go to ContactController and call a method get_link which would return something like:
{label: "Contact", url: "/contact"}
Similarly HomeController#index would try calling get_link on every other controller until it has collected an Array of link objects which could then be passed to the View to render navigation.
I tried doing this through metaprogramming in Modules and using included hook method (getting my references from a Sitepoint tutorial). But that didn't work. Then I read somewhere that I may have to use ActiveSupport::Concern for ths kind of functionality, but I'm not sure how to go about it. I've worked on PHP CMS like Drupal and WordPress any they implement this using hooks, which is more-or-less what I'm looking for here. Any ideas?

In Rails flavor MVC controllers are not the dumping ground for a bunch of miscellaneous junk. In fact the only public methods of a controller are the the actions which correspond to a HTTP request:
class ThingsController
before_action :set_thing, except: [:new, :index]
# THIS IS THE "PUBLIC API" OF THE CONTROLLER:
# GET /things
def index
#things = Thing.all
end
# GET /things/:id
def show
end
# EVERYTHING ELSE IS PRIVATE!
private
def set_thing
#thing = Thing.find(params[:id])
end
end
In fact controllers should only be instantiated by the Rails router or your controller tests. While you can create class methods and call them on your controllers this is also a huge anti-pattern.
According to the single responsibility pattern the controllers job is to just to respond to HTTP requests when called by the routing layer.
So do you solve such a case:
view partials
helper methods
initializers / config
How exactly to solve the issue depends on the use case - is the content static or are you building something CMS like?

First you need to eager load all the controllers:
Rails.application.eager_load!
then, to get the list of controllers in your app:
controllers = ApplicationController.descendants
from here you can call their class methods
controllers[0].get_link
Note that the methods should be declared as class methods like this:
def self.get_link
end
This will probably slow down your application somewhat because it eager loads everything. And in general it doesn't seem to me like a good idea, since the controllers in your app are pretty much static. Consider using path helpers instead like contact_path for example. Check out Rails Routing Docs for details.

Related

Ruby on Rails - Controller without Views

Iam new in Ruby on Rails. Normally I work with other web languages and now (of course) I try to compare it with other languages using on web.
But sometimes i have some problem to understand the philosophy and the character of Ruby on Rails. Of course i understand the concept of MVC.
But now Iam not absolutely sure:
Is ist OK to create and use a controller without views? In some cases you need a "class" for some usefull functionality used by other controllers they have views. Or is it a better style to use a module?
I try to find it out by reading a lot of articles and examples, but didnt find detailed information about this content.
When developing Ruby On Rails apps, it's recommended to put most of your business logic in the models, so for the controllers the logic need to be minimal to provide info for the views, so if it doesn't work with a view chance that you need a controller are really low.
Is it OK to create and use a controller without views
Yep it's okay.
The key thing to keep in mind is that Ruby/Rails is object orientated.
This means that every single you do with your controllers/models etc should have the "object" you're manipulating at its core.
With this in mind, it means you can have a controller without corresponding views, as sometimes, you just need to manipulate an object and return a simple response (for example with Ajax).
--
We often re-use views for different actions (does that count as not having a view):
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def search
render :index, layout: false
end
end
The notion of skinny controller, fat model is sound in principle, you have to account for the times when you may need small pieces of functionality that can only be handled by a controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find params[:id]
#user.update
respond_to do |format|
format.js {render nothing: true}
format.html
end
end
end
A very rudimentary example of Rails is a drive-thru:
view = input interface
controller = accepts order & delivers
model = gets order packaged etc in backend
There are times when the controller may not need a view (for example, if you update your order with specific dietry requirements), and thus the notion that every controller action has to have a view is false.
It's really about making your controller versatile enough to manage your objects correctly.
Shared controller methods can be placed in ApplicationController. Also, in Rails 4 there are concerns (app/controllers/concerns/) where you can put the modules with methods that can be used by multiple controllers.
Controller handles request and renders corresponding view template. So controller without view is absolutely nonsense.
Every time request will execute this controller, it will just end with missing template error, so you will need to create view folder and put empty files with action name inside of it, which is obviously stupid.

Package instance variables in rails controllers?

I'm overwhelmed by managing instance variables in controllers so am thinking if there's a better way to manage them.
My situation is, I'm having a PagesController that handles the front page rendering. In the front page, I have multiple small forms that originally belong to different controllers (For example, make a new post form, and there's a PostsController dedicated for it but for convenience you can make an easy post just at the front page.) and they all need their corresponding instance variable to hold the form (e.g. new post form needs a #post object).
It turns out to me, that I have to manually add these instance variables into my PagesController#index in order to make the forms work, so many lines become just
#post = Post.new # similar for other objects
#some_other_var = OtherController.new # another one
#one_more = AnotherController.new # again
# even more #variables here when the website is big
If this doesn't seem bad enough, think about when create or edit action fails (e.g. does not pass validation) and we need to render the previous page. We need to add these lines AGAIN. Actually we need to include ALL THESE VARIABLES whenever there's a render.
It seems very cumbersome to manually type such code to every action that needs them and it's so easy to just miss one or two of them when the website gets complicated.
So I'm wondering if there's a better way to manage such variables so that we only need to include them once instead of writing the same code every time.
You can create a before_filter something like:
class ApplicationController < ActionController::Base
...
...
protected
def instance_variables_for_form
#post = Post.new # similar for other objects
#some_other_var = OtherController.new # another one
#one_more = AnotherController.new # again
# even more #variables here when the website is big
end
end
and use it like:
class PagesController < ApplicationController
before_filter :instance_variables_for_form, only: [:action]
...
...
end
and then you can call it explicitly too from any action whenever needed.
If those variables can be logically grouped, you should consider putting them into Presenter objects.
Here is a good blog post explaining the idea: http://blog.jayfields.com/2007/03/rails-presenter-pattern.html

How do I determine the default action for a Rails controller?

I'm working on a functional test that needs to assert that a certain XHTML tag is present in a certain set of controllers. I'm basically doing this:
class ApplicationControllerTest < ActionController::TestCase
def setup
#controller = ApplicationController.new
end
# [...]
def test_foo_bar_and_baz_include_stylesheet
[FooController, BarController, BazController].each do |controller|
#controller = controller.new
get :show
assert_select 'head > link[rel="stylesheet"]'
end
end
end
The problem is that not all controllers have a :show action. What I need to do is ask either the controller or the routing configuration for the controller's default action and call get with that.
Update: Joseph Silvashy was right: I ended up separating my controller gets out into separate test cases. Solving the "default" route problem was bad enough, until I discovered that some of my routes had conditions attached, and I would have to parse them to craft the correct get call. And finally, Rails functional tests don't deal very well with calling get multiple times in the same test case, especially when that those calls are hitting multiple controllers. :(
I think the lesson here is one that we all know by heart but sometimes is hard to accept: if the code looks hairy, you're probably doing it wrong. ;)
Controllers don't have a "default" view or action, additionally actions can be named anything you want, they don't have to be the standard index, show, new, etc...
I'll probably have to :get the appropriate action for each controller you want to test. It's likely each test will be different down the road anyhow, even though right now they all have the same requirement, I think it makes sense to write one for each action regardless.
try to use the respond_to function: http://www.ruby-doc.org/core/classes/Object.html#M001005
to check if a method exitst.

Static pages in Rails?

So I'm wondering what the best way to do static pages in Rails is, or rather Rails 3. I've always been a little confused about this, like should I create a controller or not?
There are a variety of approaches you can take (they're not all good approaches).
1 - public directory
If it's truly static, you can put it in the public directory. Things in the public directory will be served immediately, without going through the Rails stack.
Advantages:
Because it doesn't have to waste time going through the Rails stack, the client will receive a faster response.
Disadvantages:
You won't be able to use your sites layout. Or view helpers like link_to.
Instead of your views being in one place (app/views), now they're in two places (app/views and public). This can be confusing.
Thoughts: I feel pretty strongly that the disadvantages outweigh the advantages here. If you're looking to make a minor improvement in speed at the expense of readability and programmer happiness, why use Rails in the first place?
2 - Place in app/views and render directly from the Router
It is possible to render views from the router. However, it definitely isn't The Rails Way.
From the official RailsGuide on routing:
1 The Purpose of the Rails Router
The Rails router recognizes URLs and dispatches them to a controller's action.
Architecturally, there isn't anything inherently wrong with having a router map directly to a view. Many other frameworks do just that. However, Rails does not do that, and deviating from an established convention is likely to confuse other developers.
like should I create a controller or not?
Unless you want to take one of the approaches mentioned above - yes, you should create a controller.
The question then becomes what to name the controller. This answer outlines some options. I'll list them here with some thoughts. I'll also add three other options.
3 - Use ApplicationController
# routes.rb
get "/about" to: "application#about"
# application_controller.rb
class ApplicationController < ActionController::Base
def about
render "/about"
end
end
# app/views/about.html.erb
The advantage here is that you don't introduce overhead/bloat by creating a new controller and folder. The disadvantage is that it's not The Rails Way. Every controller you create inherits from ApplicationController. ApplicationController is typically used to house functionality that you want to share between all other controllers. See this example from the Action Controller Overview Rails Guide:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
4 - StaticController or StaticPagesController
Michael Hartl's popular Ruby on Rails Tutorial uses a StaticPagesController. I agree with the source I got this from in that I don't like this approach because the pages often aren't actually static.
Also, there is a possibility of confusion - why did we put other static views in separate controllers? Shouldn't static views be rendered from the StaticPagesController? I don't think the possibility of confusion is too high, but still wanted to note it.
Also note Hartl's footnote:
Our method for making static pages is probably the simplest, but it’s not the only way. The optimal method really depends on your needs; if you expect a large number of static pages, using a Static Pages controller can get quite cumbersome, but in our sample app we’ll only need a few. If you do need a lot of static pages, take a look at the high_voltage gem. ↑
5 - PagesController
The official Ruby on Rails routing guide uses PagesController. I think this approach is fine, but it isn't descriptive at all. Everything is a page. What distinguishes these pages from the other pages?
6 - UncategorizedPagesController
I would call the controller UncategorizedPagesController, because that's exactly what they are - uncategorized pages. Yes, it's a little more cumbersome to type and read. I prefer the advantage of clarity over conciseness, but I could understand the choice to be more concise and go with PagesController, or something else.
7 - High Voltage gem
With High Voltage, you don't have to do the tedious work of writing out routes and empty controller actions:
# routes.rb
root 'pages#home'
get '/about', to: 'pages#about'
get '/contact', to: 'pages#contact'
get '/help', to: 'pages#help'
get '/terms-of-service', to: 'pages#terms_of_service'
get '/landing-page', to: 'pages#landing_page'
...
# pages_controller.rb
def PagesController < ApplicationController
def home
end
def about
end
def contact
end
def help
end
def terms_of_service
end
def landing_page
end
...
end
You just add your pages to app/views/pages and link to them: <%= link_to 'About', page_path('about') %>.
It depends on if they're truly static. You can always add pages to the public/ directory of your app, and they'll work just fine. They don't even fire up rails or touch the routing engine.
However, most pages on the site, including static, still need to use the site's layout. You don't want to have to update the layout in dozens of pages separately. In this case, you can create a "catchall" controller. Here's an example:
rails g controller site home about_us location
Then you can put the page-specific content in app/views/site/home.html.erb, for instance.
UPDATE: You can go a step further and cache those pages with a call to caches_page near the top of your controller:
class SiteController < ApplicationController
caches_page :home, :about_us, :location
end
Just be aware that if you have dynamic page elements, like a list of links that changes based on whether the user is logged in, page caching isn't going to work for you. But this info should get you pointed in the right direction.
If they're 100% static, just shove them into public.
For example, when you spin up a new rails project, there's an index.html in your public.
If you want nicer routing, then yeah, creating a controller probably wouldn't be a bad idea.
I prefer to create a controller. Good tutorial and explanation on static pages:
http://railstutorial.org/chapters/static-pages#top

using helpers properly in ruby on rails

It looks like you cannot use a helper in a controller even if both of them belong to the same class. For example: XYZHelper and XYZController...
I was under the impression that if the prefix is the same "XYZ" then the method in the helper can be used in the controller and in the view, but I think this is not the case.
So how do I remove some common functionality from a controller and place it in a helper. I want to place that piece of code in a helper because other controllers may be using it. What is the best way to approach this.
Thanks,
Jai.
There are a few ways you could share some code between controllers:
Application controller: If the code in question is an action/method which ought to be in a controller, but could be used by several controllers (or all of them), then this might be a place to put it.
the 'lib' directory. just a general purpose place to put code which should be shared.
Put it in the model. This may or may not be applicable, but its worth taking a good look at the code you're trying to move and thinking about whether it is something which makes sense on a model (instead of a controller or random class/module in lib).
Follow Pete's guidelines. If you still need to expose the methods then do the following:
Add the methods to ApplicationController class and register the methods as helper methods by calling helper_method.
class ApplicationController < ActionController::Base
helper_method :foo, :bar
private
def foo
"foo"
end
def bar
"bar"
end
end

Resources