Static pages in Rails? - ruby-on-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

Related

Rails, hooking into controllers

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.

what determines controller action ruby rails

at https://www.codecademy.com/en/courses/learn-rails/lessons/start/exercises/start-views, the controll action is described as 'pages#home':
Well done! Now when a user visits http://localhost:8000/welcome, the route
get 'welcome' => 'pages#home'
will tell Rails to send this request to the Pages controller's home action.
but when I made the controller I did rails generate controller Pages which is uppercase.
pages_controller.rb:
class PagesController < ApplicationController
def home
end
end
Is the pages part of pages#home determined by the first part of pages_controller.rb, ignoring the _controller.rb end?
What happens if I change pages_controller.rb to renamedpages_controller.rb but leave the class name as PagesController?
thank you
Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
Controller Naming Convention
The naming convention of controllers in Rails favors pluralization of the last word in the controller's name, although it is not strictly required (e.g. ApplicationController). For example, ClientsController is preferable to ClientController, SiteAdminsController is preferable to SiteAdminController or SitesAdminsController, and so on.
Following this convention will allow you to use the default route generators (e.g. resources, etc) without needing to qualify each :path or :controller, and keeps URL and path helpers' usage consistent throughout your application. See Rails Guides.
When a request is made like: http://localhost:8000/welcome, it matches a route in the routes.rb file where the route is mapped to a controller and an action.
In your routes file, you have:
get 'welcome' => 'pages#home'
get 'welcome' part matches with the url .../welcome and map this request to pages#home where pages is the controller name and home is an action (a method defined in pages_controller). This is Rails convention to name the controllers like this: ControllerName_controller.rb. So, basically pages is your controller's name and the last _controllers is common to each and every controller in your Rails application.
What happens next is, this action of the controller do some work and talk to the model/database and build the required variables/data that will be passed to a view. According to Rails convention, there has to be a matching view file like: home.html.erb which will render the response to the browser.
There are other ways to render different partials and views in different ways, but if you follow the Rails convention, then it becomes very easy to understand and work with different Models, Views and Controllers in Rails. This is called: Convention Over Configuration.
When you follow the Rails convention for naming things, Rails does a lot of work and configuration for you for free to make your life easier.
When you have get 'welcome' => 'pages#home' in your routes file, then when a request: /welcome comes, it maps to pages_controller and it will look for the pages_controller.rb file under the app/controller/. If you rename this to something else, then the program will not find it as it expected and will throw you an error and your request will not be completed.
Your controller's name has to match with the class name of that controller, This is also a Rails convention. If you change any one of them, then you will get an error and your request will fail.
Yes. and #home is the "action" in PagesController
You get a uninitialized constant PagesController error
So, your controllers should always be in the form NameController defined in name_controller.rb, and actions as public methods in NameController.

what happens behind the scenes of the 7 default ruby on rails routes?

on codeacademy I see 7 default rails routes like new, show, index, etc...
But when you declare these routes in the controller it seems you still enter the info by hand.
Like here, it wants me to name a #tag out...normally you feed it the model and it just knows:
class TagsController < ApplicationController
def index
#tags = Tag.all
end
def show
#tag = Tag.find(params[:id])
#destinations = #tag.destinations
end
end
Do naming your controller functions within the 7 default routes automate things I can't see? So if I renamed 'show' 'showsomestuff' but left the rest the same, does it work differently?
If not, what is the purpose of naming this routes as such if it doesn't automate anything? Is it just convention?
I'm used to django classbased views hiding a lot of cruft after feeding it a model or object ID, thanks
In my experience, and I've worked on some large rails apps, this ends up being a good thing. As you grow, what gets inserted into the #tags variable in your example gets more complicated, such as:
def index
#tags.for(current_user).most_popular(10) # ... etc
end
If you're looking to eliminate boilerplate code, your best bet is to use generators, which will automatically fill out your controllers:
rails generate controller Tags
Naming does nothing by itself. Rails normally uses a callback:
before_action :set_tag, only: [:show, :edit, :update, :destroy]
...
def set_tag
#tag = Tag.find(params[:id])
end
You can actually see this added to your generated code.
Convention over configuration
There isn't really any such thing as "default rails routes." You could have any, all or none of the actions you listed.
To your question: "If not, what is the purpose of naming this routes as such if it doesn't automate anything? Is it just convention?"
Yes. It is convention. Convention over configuration is a core tenet of Ruby on Rails.
Ruby on Rails® is an open-source web framework that’s optimized
for programmer happiness and sustainable productivity. It lets you
write beautiful code by favoring convention over configuration.
You'll find little automation in RoR, but a lot of convention, which makes order of the underlying complexity. Naming conventions are critical, which is where you see the "index", "show" etc. action names. They aren't defaults, and in fact could be "indyx", "bob", etc. but that would violate convention over configuration.
I recommend, if you're getting started in RoR that you grab hold of the intro spec and really grasp it. The model, controller and view naming conventions are doctrine. You could rebel, but your code will be unmaintainable and you may become an outcast (just kidding about the last part) but it is important.

Rails: Is it bad practice to handle CRUD operations for two models in one Controller?

I am working on a Rails project. I was advised to make one controller, the Home_Controller, which would handle the requests of the site. I have two different models, Post and Person (which btw are totally independent). I would like to define methods like new in the Home Controller but it seems against convention to write controller methods like new_person and new_post.
Thanks for your help!
It IS against the MVC pattern, as your Home_Controller should only control the Home model.
You should have a PeopleController and a PostsController to separate your concerns.
That being said - it's not totally unheard of to have the system that you are asking for.. You'll just have to create your own custom routes in routes.rb to match what you want. For example, your HomeController could look like,
class HomeController < ApplicationController
...
def new_person
#person = Person.create
end
def new_post
#post = Post.create
end
end
Routes would look something like,
get 'people/new' => 'home#new_person'
get 'post/new' => 'home#new_post'
the main issue is that when you stray away from this convention, you run into very unreadable and hard to maintain code. especially when you have multiple hands in 1 file.
Going to go ahead and say probably. It's hard to know exactly outside of context, but yes, this would go against convention.
Separate from the convention issue, is maintainability and readability and having one massive controller file would be hell to develop on.

Rails routing and URI fragment identifier

When I was developing my RoR skills with some basic tutorials I encountered a problem. What I am trying to achieve is having comments belonging to posts, with no separate index or individual view. This part was easy.
Here comes tough one. I want post_comment_url to return address with fragment identifier: http://example.com/posts/2#comment-4. It would allow me to use redirect_to in it's simplest form, without :anchor parameter (which would be against ruby way of keeping things simple).
How to do that?
Instead of altering Rails' default behavior, it'd probably be better to wrap up your needs in a helper method:
# in app/controllers/application_controller.rb
class ApplicationController
helper :comment_link
def comment_link(comment)
post_comment_url(comment.post, comment, :anchor => "comment-#{comment.id}")
end
end
The call to helper will allow you to access that method in your views as well as your controllers.

Resources