What are the standard way of making a Ruby on Rails application that will have pages such as
Home
About
Contact
I would appricate if someone had links or an answers rather than just say use a gem because I want to learn how to make simple webapps with such behavior.
Depends on how you want to handle the content in those pages.
Approach #1 - store content in views
If you just want to put all your content in ERB views, then a very simple approach is to create a PagesController whose purpose is to deal with static pages. Each page is represented by one action in the controller.
pages_controller.rb:
class PagesController < ApplicationController
def home
end
def about
end
def contact
end
end
routes.rb:
match '/home' => 'pages#home'
match '/about' => 'pages#about'
match '/contact' => 'pages#contact'
Then create home.html.erb, about.html.erb, and contact.html.erb views under app/views/pages. These views contain whatever content you want on your static pages. They'll by default use your app's application.html.erb layout.
You'll also want to look into page caching to give yourself a boost in performance.
Approach #2 - store content in database
Another approach I've used is to make a very basic CMS for static pages. In this case, pages are represented in the model. It uses the friendly_id gem to handle slugs for each page so that they can be retrieved by a pretty name in the URL (e.g., /about) rather than by ID.
page.rb:
class Page < ActiveRecord::Base
attr_accessible :title, :content
validates_presence_of :title, :content
has_friendly_id :title, :use_slug => true, :approximate_ascii => true
end
pages_controller.rb:
class PagesController < ApplicationController
def show
#page = Page.find(params[:id])
render 'shared/404', :status => 404 if #page.nil?
end
end
show.html.erb:
<%= raw #page.content %>
routes.rb:
match '/:id' => 'pages#show'
Note: put this entry at the end of routes.rb since it matches everything.
Then how you want to create, edit and update pages are up to you - you can have an admin interface, or build it in to your public interface somehow. This approach can benefit from page caching too.
Another option is the high_voltage gem: https://github.com/thoughtbot/high_voltage
This makes it super easy to create static pages where the content is stored in views.
Jeff's approach #1 (storing content in views and having a route and controller action for each static page) is a good one. The only thing I would add is to use the controller macro in your routes.
So, instead of this:
match '/home' => 'pages#home'
match '/about' => 'pages#about'
match '/contact' => 'pages#contact'
You can do this:
controller :pages do
get :home
get :about
get :contact
end
It's two extra lines, yet much so much more elegant, since it eliminates repetition and groups your static page routes together visually.
It also uses the get http verb method instead of match, which is a better practice for Rails routes (and more concise, now that Rails 4 requires the http verb to be specified when using match.
Jeff's Approach #1 works great for me. Here is a trick to make the controller dynamically look up pages. With this, you don't need to touch the controller nor the routes.rb for adding pages. Just drop the pages under app/views/pages and the controller will find it.
class PagesController < ApplicationController
def show
render params[:id]
end
end
Check out Michael Hartl's http://railstutorial.org
which comes in a 2.3.8 and 3.0.x version. It covers this with great examples and leads you through building them very early on and you will also have the opportunity to learn a lot more than this example.
I highly recommend it.
config/routes.rb
get ':id', to: 'pages#show'
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def show
begin
render params[:id]
rescue ActionView::MissingTemplate
render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found
end
end
end
Then place your static pages in app/views/pages/{name}.html.erb (or whatever template format.)
An adequate answer to your question would basically look like an introduction to the Rails framework: the MVC structure, templating, and routing DSL at least. Jeff has given a good stab, but his answer still assumes a lot of basic Rails knowledge on your part.
I'd suggest though, that if your webapp is really that simple, Rails might be overkill. I'd look into something lighter, like Sinatra, which has a much lower learning curve than Rails and does a great job of this kind of thing without having to deal with complex routing, magic MVC action/template mapping, etc.
I'd suggest adding your pages in the public folder so as to be served directly without having to pass through rails at all. I'm not an expert though so I'm not sure if this could have any cons if the page is static.
For more you can create static pages using Jekyll bootstrap or also Jekyll using Danger blog
Refer it is very helpful.
Related
I'm developing a site using refinery. Now for one specific page that is created in the back-end of refinery, i want to use my own controller and views. All the User can do with this page is to set the menu-position, title, meta-info etc. The URL for this page has to look the same as all the other pages.
So for example, the menu structure looks like:
menux
menu1
menu2
specific page
menux
And the URL for "specific page" looks like "locale/menu1/menu2/specific page"
The site is available in multiple languages, so i have to create these routes for all languages.
Currently i'm creating the routes like this:
specific_page_id = 1
Refinery::I18n.frontend_locales.each do |lang|
slugs = []
page = Refinery::Page.find_by_path_or_id(nil, specific_page_id)
# get slug for page in current language
slugs << page.translations.select { |p| p.locale == lang }.first.slug
# get all slugs from parrent pages
while !page.parent_id.blank?
page = Refinery::Page.find_by_path_or_id(nil, page.parent_id)
slugs << page.translations.select { |p| p.locale == lang }.first.slug
end
match "/:locale/#{slugs.reverse.join("/")}" => "controller#action", :via => :get, :constraints => { :locale => /#{lang}/ }
end
With this, i'm getting a route to the specified page in every language like described above.
But the problem is, when the user changes the name of the page or the position in the menu, the routes have to be generated again, which isn't done too often.
Now my question is, how can i do this more dynamically on run-time? I've read a bit about constraints but i don't know if this is what i need.
Thanks for your help!
I needed to figure out building routes off a database model myself in a Rails 4 application (which is called "ComingSoon" in the examples below. I wanted pages that could be edited on the back-end and given a user-friendly name, which is stored in the Page#name field. So "About Us" titled page typically becomes "about_us" name, which leads to "http://localhost:3000/about_us" The following is the technique I came up with:
Create a new model in app/models/dynamic_router.rb
class DynamicRouter
def self.load
ComingSoon::Application.routes.draw do
Page.all.each do |pg|
get "/#{pg.name}", :to => "pages#show", defaults: { id: pg.id }, as: "pages_#{pg.name}"
end
end
end
def self.reload
ComingSoon::Application.routes_reloader.reload!
end
end
The key above is that I pass the page's id as one of the parameters, so look up is still on the Page#id field, which is, IMHO, a lot better than using the friendly plugin or lookups on slugerized values.
Add the following line to your config/routes.rb
ComingSoon::Application.routes.draw do
# ...
DynamicRouter.load
end
Finally, when the Page is updated, we need to reload the routes, so add an after_safe callback on the Page model:
class Page < ActiveRecord::Base
after_save :reload_routes
def reload_routes
DynamicRouter.reload
end
end
I plan to refine this further to only reload routes if the name attribute is changed and perhaps simply edit the existing route rather than reloading everything if performance proves to be an issue (which at the moment, its not).
What's the Rails way of displaying the views for a somewhat deeply nested hierarchy? Seems like a simple question but try to bear with me and maybe someone will understand (and hopefully clear up) my confusion.
My app is a simple hierarchy, of sorts (simplified more so for purposes of this discussion):
At the top I have an index of documents
click one and get a view with more details about the Document including a list(index) of its chapters
click a chapter from that list and get a Page with previous & next links and similar list of chapters in the margin with current one highlighted.
Simple enough but my question is what's the rails way to:
nest the resources so they're not too deep?
structure the views?
The reason I ask something seemingly so basic is because, starting with the first click I'm viewing a 'Document' (document#show) but also the chapters (chapters#index) that it contains. So, in which of the two should the view be built?
Same with the next click where I'm looking at the beginning of a Chapter. That could be the chapters#show or pages#index action. Though, conceivably I guess it could (and maybe should) also be handled by pages#show.
Thoughts? Am I over thinking things? (likely)
Routes
You can set up your routes like this to take advantage of Rails' built in nested resources:
resources :documents, :only => [:index, :show] do
resources :chapters, :only => :show do
resources :pages, :only => :show
end
end
This is roughly equivalent to setting up these custom routes:
get '/documents' => 'documents#index',
:as => 'documents'
get '/documents/:id' => 'documents#show',
:as => 'document'
get '/documents/:document_id/chapters/:id' => 'chapters#show',
:as => 'document_chapter'
get '/documents/:document_id/chapters/:chapter_id/pages/:id' => 'pages#show',
:as => 'document_chapter_page'
If the URLs are a little longer and more cumbersome than you had hoped for, or you want to use custom parameters (e.g. identifying pages by number and not by ID), you can always write custom routes instead:
resources :documents, :only => [:index, :show]
get '/documents/:document_id/:id' => 'chapters#show',
:as => 'document_chapter'
get '/documents/:document_id/:chapter_id/:page' => 'pages#show',
:as => 'document_chapter_page'
See the routing guide for more information.
Controllers
You don't want a user to be able to visit a chapter directly, instead they should see the first page of the chapter. However, it's arguably useful for chapter URLs to work without a page number (especially if page numbers refer to the position in a document and not the position in a chapter).
You could therefore redirect from the chapters controller's show action to the first page:
class ChaptersController < ApplicationController
def show
chapter = Chapter.find(params[:id])
redirect_to [chapter.document, chapter, chapter.pages.first]
end
end
Views
There are two approaches you could take to sharing the list of chapters between different views:
Use Rails' built in support for rendering a collection of model objects. In your views/documents/show.html.erb and views/chapters/show.html.erb you can include something like the following (I'm assuming each action sets a #document variable):
<ol>
<%= render #document.chapters %>
</ol>
Rails will look for a partial view called views/chapters/_chapter.html.erb, and render it for each of the chapters. It could look like this:
<li><%= link_to chapter, [chapter.document, chapter] %></li>
Share the whole list in a single partial. For example, you could add the following to views/documents/show.html.erb and views/chapters/show.html.erb:
<%= render 'chapters/list', :chapters => #document.chapters %>
And create a partial view called views/chapters/_list.html.erb:
<ol>
<% chapters.each do |chapter| %>
<li><%= link_to chapter, [chapter.document, chapter] %></li>
<% end %>
</ol>
See the section on Using Partials in the Layouts and Rendering guide for more information.
I've read more than once that nesting shouldn't be abused, 2 levels is obviously fine, three levels if it makes sense, but anything over three levels, and you probably need to rethink things.
You could do with three levels of nesting, i.e.
/docs/:id/:chapter/:page
But it might also make sense to do this
/docs/:id/:page
i.e. directly reference a page of a document, not caring to specify the chapter
If chapter is a number, and page is a number, how would your routing differentiate, i.e.
/docs/1001/1
Is that page 1 of doc 1001 ? Or chapter 1 of doc 1001?
Just playing devil's advocate, showing potential shortcomings when you try and nest deeply.
I think the most natural thing is:
'/docs' => 'docs#index' # show all docs
'/docs/:id' => 'docs#show' # show all chapters in a doc
'/docs/:id/chapter/:chpt' => 'docs#show_chapter' # show all pages in a chapter in a doc
'/docs/:id/:page' => 'docs#show' # show a page in a doc
the show actions would have:
if params[:page]
# show page specified
else
# show all chapters
end
the order of the routes is important
The reason I'd recommend this is because book/page query semantics is probably more common than book/chapter/page.
The associations would be:
class Doc < ActiveRecord::Base
has_many :pages
has_many :chapters
end
I wouldn't expose end_page in the routing, I'd just tack '?endpage=X' on any route that accepts :page, so for example in the show action:
if params[:page]
if params[:end_page]
# show pages :page through :end_page
else
# show single :page
end
else
# show all chapters
end
EDIT: Sorry, the title is a little unclear, I wanted to use 'semi-static' pages, using the render helper and ruby vars. The ERB templating system etc. Sorry guys! My fault!
I've been looking into creating nested semi-static pages for a rails 3.1.3 app I'm building, and I've yet to find an answer that would suit all my needs. Any ideas?
All of the solutions I've come across are about creating just top level pages, like so:
- Site root
--- About (http://wwww.example.com/about)
--- Contact (http://wwww.example.com/contact)
--- Products (http://wwww.example.com/products)
--- Pricing (http://wwww.example.com/pricing
Whereas I'm looking to do something like
- Site root
--- About (http://wwww.example.com/about)
------ What we do (http://wwww.example.com/about/what-we-do)
------ Another sub-page (http://wwww.example.com/about/another-sub-page)
--- Contact (http://wwww.example.com/contact)
--- Products (http://wwww.example.com/products)
------ Product One (http://wwww.example.com/products/product-one)
------ Product Two (http://wwww.example.com/products/product-two)
--- Pricing (http://wwww.example.com/pricing)
I've come across solutions like mapping static controllers for each of the pages, but that doesn't seem like a particularly elegant solution.
Or creating a generic route and controller to match requests, like so:
in routes.rb:
map.connect '*path', :controller => 'content', :action => 'show'
in content_controller.rb:
def show
render :action => params[:path].join('/')
end
But that seems even more inelegant, is there another way I'm missing?
is there another way I'm missing?
Yes.
All you have to do is create static pages as you require in /public, either in the root of public, or in a directory structure.
A physical file existing at a path under /public should override any routes you configure to dynamically generated pages.
What DanSingerman said, but also... Just put your static pages on a separate fqdn, possibly hosted on a cdn. The only reason to have rails serve static assets is that you're being lazy and just can't be bothered doing it the right way.
I faced a similar problem for static sub pages, came up with the following solution.
In routes.rb
match 'about/(:page)', :to => 'about#show', as: 'about'
match 'about/what-we-do', :to => 'about#show', as: 'what_we_do'
In about_controller.rb
class AboutController < ApplicationController
def show
unless params[:page].blank?
render "about/#{params[:page].underscore}"
end
end
end
In your views you can reference the alias paths.
<%= link_to 'About', about_path %>
<%= link_to 'What We Do', what_we_do_path %>
So /about will default to rendering the view about.html.erb.
But /about/what-we-do will render the view about/what_we_do.html.erb.
Would something like this help solve your problem?
That is in fact what most CMS's do. If you feel that you are doing a lot of heavy lifting try plugging in a CMS like Refinery to your app. It makes life a little simpler by taking care of some of the SEO aspects. If you are interested in how Refinery CMS handles its pages, have a look at the Pages Controller and the related routes and the magic match all route.
I'm looking to "automatically" generate routes based on actions defined in a controller. resources routing (as far as I know) only automatically generates routes & helpers for http verbs. In this instance I'm serving mostly static pages using rails and have no need for http verbs in my controller.
Specifically:
In a controller I have defined actions which refer to those mostly static pages.
def action
end
In the routes file I have a bunch of
match "/url" => 'controller#action'
I'd like all those matched routes to be generated automatically based on the actions in the controller. Something CONCEPTUALLY along the lines of:
for actions in controller.erb do |action|
'match "/action" => "controller#action"
end
Is this possible? Would I write the code in the routes file directly?
I also have some nested actions to consider... a controller action may be:
def action
def nested_action
end
end
I'd appreciate any thoughts on this matter. Thanks.
What's wrong with the normal /:controller/:action idea?
That won't deal with nested actions, but... I'm having a difficult time understanding why you'd ever want that.
You can do something like this:
controller :controller_name do
get "path/action" => :method_name, :as => :path_action
post "path/save" => :method_name, :as => :path_save
end
That is, you can group different routes within a controller using the method above.
I'm attempting to create "pretty URLs" for linking to posts in a blog. I want to maintain access to the blog entries via entries/23 and 2011/07/some-post-slug-here as I only generate a slug once an entry has been published (just in case the title of the posts changes, and, though not a strict requirement, I would prefer to be able to edit/delete posts via the entries/23 style URL. Currently, the appropriate part of what I have in my config/routes.rb:
root :to => 'entries#index'
resources :entries
match ':year/:month/:slug' => 'entries#show', :constraints => {
:year => /[0-9][0-9][0-9][0-9]/,
:month => /[0-9][0-9]/,
:slug => /[a-zA-Z0-9\-]+/
}, :as => :vanity_entry
and I use this (in my application helper) function for creating the links:
def appropriate_entry_path entry
if entry.published
vanity_entry_path entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_path entry
end
end
def appropriate_entry_url entry
if entry.published
vanity_entry_url entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_url entry
end
end
Basically, I check if the article is published (and therefore has a slug) and then use that URL/path helper, or otherwise use the default one.
However, when trying to use this, I get the following from Rails:
No route matches {:slug=>"try-this-one-on-for", :month=>"7", :controller=>"entries", :year=>"2011", :action=>"show"}
I have tried a few different solutions, including overriding to_param in my Entry model, however then I would have to create match routes for the edit/delete actions, and I would like to keep my routes.rb file as clean as possible. Ideally, I would also love to lose the appropriate_entry_path/appropriate_entry_url helper methods, but I'm not sure that this is possible?
Is there any thing I am missing regarding routing that might make this easier and/or is there any specific way of doing this that is the cleanest?
Thanks in advance.
You might want to take a look at friendly_id. It's a gem for creating seo friendly slugs :)
I found the issue with what I had been doing, the regex for :month in the route wanted two numbers, whereas I was only passing in one number. Anyways, I decided that the URLs look nicer (in my opinion) without the month padded to 2 digits, so I updated my route accordingly.