I am developing a app in ruby on rails for a local business. The pages are 'static', but changeable through a backend CMS I am building for them. Is there a best practice to creating a controller for static pages? Right now I have a sites controller with all static routes, like this.
routes.rb
get "site/home"
get "site/about_us"
get "site/faq"
get "site/discounts"
get "site/services"
get "site/contact_us"
get "site/admin"
get "site/posts"
or would I be better off creating member routes for the site controller like this without the crud, because a 'Site' will not need to have the CRUD.
resources :sites, :except => [:index, :new, :create, :update, :destroy]
member do
get :home
get :about_us
get :faq
get :discounts
get :services
get :contact_us
get :admin
get :posts
end
Or is there a best practice / better way? Any answers would be appreciated. Thanks
If the static pages list are not going to increase, then you can keep the list, but if you want a dynamic list like site/any_new_url , save the routes as
get 'site/:cms_page' => 'cms#show' # all requests matching site/any_page will go CmsController, show method
This will help reduce keep the routes from bloating, but the downside is you do not know what all routes are the valid ones. Your sample code can be
def show
#page_data = Page.find_by_page(:params[:cms_page])
end
show.html.erb
<%= #page_data.html_safe %>
Dunno yet if I consider this a best practice or an abomination but here is what I came up with when tackling the same problem.
My reasoning is that the site was providing some specified functionality (which doesn't really matter for this discussion) + a bunch of information about the organisation itself (about us, contact, FAQ, homepage blurb, whatever). Since all that data was really related to the organisation, an Organisation model seemed reasonable with each of those things as attributes. Here is the model:
class Organisation < ActiveRecord::Base
...validations stuff...
def self.attrs_regex
Regexp.new(self.attrs.join("|"))
end
def self.attrs
self.column_names.reject{|name| name =~ /id|created_at|updated_at/}
end
end
Then I use the attrs class method to generate routes based on the columns. This is in my routes.rb:
Organisation.attrs.each do |attr|
get "#{attr}" => "organisation##{attr}", :as => attr.to_sym
get "#{attr}/edit" => "organisation#edit", :as => "#{attr}_edit".to_sym, :defaults => { :attribute => attr }
post "#{attr}" => "organisation#update", :as => :organisation_update, :defaults => { :attribute => attr}, :constraints => Organisation.attrs_regex
end
The controller gets a little weird and I am not thrilled with the code here but here it is anyway. I need to make sure the attribute is set and available to the views so I can do the right thing there so I set it in the application controller:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_attribute
def set_attribute
#attribute = action_name.parameterize
end
end
For the organisation controller I just set the #organisation variable to be the first and only row in the database in the before_filter and then let Rails do its usual magic of calling the method, failing, and rendering a view of the same name. The edit action just uses one view file to edit all the different attributes:
class OrganisationController < ApplicationController
before_filter :set_organisation
def edit
authorize! :edit, #organisation
#attribute = params[:attribute].parameterize
end
def update
authorize! :update, #organisation
#attribute = params[:attribute]
respond_to do |format|
if #organisation.update_attributes(params[:organisation])
format.html do
redirect_to "/#{#attribute}", notice: t('successful_update')
end
format.json { head :ok }
else
format.html { render action: "edit" }
end
end
end
private
def set_organisation
#organisation = Organisation.first
end
end
So that is where I ended up. Like you I hit up SO to tap into the seething mass of genius here but ended up with disappointing results. If there is something better out there I am still hoping to find it.
What I like about what I did is that routes are automatically generated based on the structure of the organisation table.
What I don't like about what I did is that routes automatically generated based on the structure of the organisation table.
I know I will pay for that design decision when I have to deal with i18n routing and there are probably a thousand other reasons that this is a bad idea that I have yet to discover but for the moment I have a happy client.
In the end this is not a suggestion that you should do this, but I am hoping to give you more than I got so you can advance your thinking on this and hopefully end up a little closer to that best practice.
If you are going to construct a CMS, which likely connects to a database, and allow your customer to change the text on the pages of their site, I would not recommend using static pages. In Rails terms, a static page would refer to creating html files in your /views/pages directory. If you go this route, then you're walking outside of the way that Rails was designed.
I believe that what you want to do is create tables in the database that correspond to and store the data for your posts, etc. You can pull information into the controller from the model that it corresponds to and then user a view to display the data. You can create a layout for these pages and then create controllers for each of the pages that you add.
As far as routes, I would recommend using the following:
map.resource :controller_name
you then would add the code that displays the information from the CMS in the corresponding show controller action and view for each page.
Related
In my Rails routes.rb file I'm wanting to do something like the following.
get '/:id' => 'pages#show'
get '/:id' => 'articles#show'
So that if a visitor types in
http://www.example.com/about-this-site
The pages controller in the above example would get first shot at handling it. Then if not, the next controller in line would get a shot.
REASONs for wanting to do this:
1) I'm trying to port my Wordpress site over without establishing new urls for all my pages and blog posts. As it stands, all of my blog post files and pages are accessed directly off the root uri '/' folder.
2) Because I'm not able to, it's a learning thing for me. But, I want to do it without a hack.
How about redirecting to the second controller from your first controller?
in PagesController
def show
unless Page.find_by(id: params[:id])
redirect_to controller: :articles, action: :show, id: params[:id]
end
end
in ArticlesController
def show
# Handle whatever logic here...
end
Edit
If you really don't want to redirect then you can consolidate the logic into a single action:
def show
if Page.find_by(id: params[:id])
render :show
elsif Article.find_by(id: params[:id])
render controller: :articles, action: :show
else
# Handle missing case, perhaps a 404?
end
end
However, I'd recommend using a redirect if possible. It's a cleaner solution and keeps your controller code isolated.
Have a Help system for a Rails App, that uses a static page controller.
def show
if valid_page?
render template: "help/#{params[:page]}"
else
render file: "public/404.html", status: :not_found
end
with route
get 'help:page' => 'help#show', :via => [:get]
The Help folder started to become overwhelming with all the static views for the application.
So I wanted to split the views into sub-folders with associated controller - so in the Help folder is now
---Welcome
----index.html.erb
----about.html.erb
----contact.html.erb
---Blog
----index.html.erb
etc
Under the help folder there are two dozen or so sub folders each with 3-6 help files, without creating a route for each subfolder, is there an way to have a single smart route to reference controller(folder) and page.
get 'help:folder:page' => 'help#show', :via => [:get]
def show
if valid_page?
render template: "help/#{params[:folder]}/#{params[:page]}/"
else
render file: "public/404.html", status: :not_found
end
Any ideas?
Here's how it should work:
#config/routes.rb
resources :help, only: [:index, :show] #-> url.com/help/:id
This will allow you to use the following:
#app/controllers/help_controller.rb
class HelpController < ApplicationController
def show
#page = Help.find params[:id]
#no need to rescue this, rails will automatically throw a 404 error if not found
end
end
#app/models/page.rb
class Page < ActiveRecord::Base
#columns id | type | title | body | created_at | updated_at
end
#app/models/help.rb
class Help < Page
end
The main issue you have is that you're storing each page as an .html.erb file. Whilst this will be okay in certain circumstances, in this case it's going to get very messy, very quickly.
You'll be much better creating a Model and table to store the help pages you need, allowing you to collate & invoke them as required. An added benefit to this will be that there will be no validation (Rails will handle it all), and you'll be able to use one view file to get it working:
#app/views/help/show.html.erb
<%= #page.title %>
<%= #page.body %>
I have such method controller:
class Admin::CarManufacturersController < ApplicationController
def edit
#man = Manufacturer.find(params[:id])
render :layout => 'admin'
end
def update
#man = Manufacturer.find(params[:id])
if #man.update_attributes(params[:car_manufacturer])
****
else
render :action => :edit, :layout => 'admin'
end
end
end
and i have such route:
namespace :admin do
resources :car_manufacturers do
###
end
end
and such form partial:
= form_for [:admin, #man] do |f|
###
but when i call this form to edit my data i get:
undefined method `admin_manufacturer_path'
but i need admin_car_manufacturer_path i thing it's becouse i use other model name in controller, but i can't change it... how can i use right pass? i try to write admin_car_manufacturer_path in form, but i think this is bad idea. How to solve my problem?
I would think about renaming your controller/your model to match. Both should either be just manufacturer or car manufacturer. Having the same names for a resource's controller and model will spare you problems like the one you're having right now.
In any case, if you just need a quick fix, you can get around this by specifying the as option for your nested routes like this:
namespace :admin do
resources :manufacturers, as: :car_manufacturers do
###
end
end
Source: Rails Routing from the Outside In - Ruby on Rails Guides - 3.6: Naming Routes
That will turn your path names into admin_car_manufacturer_path etc and should allow you to use your form the way you you intended to. But I really recommend renaming your model and controller so that they match.
I have three roles: Instuctor, Student, Admin and each have controllers with a "home" view.
so this works fine,
get "instructor/home", :to => "instructor#home"
get "student/home", :to => "student#home"
get "admin/home", :to => "admin#home"
I want to write a vanity url like below which will route based on the role of the user_id to the correct home page.
get "/:user_id/home", :to => "instructor#home" or "student#home" or "admin#home"
How do I accomplish this?
I'm providing an alternate approach as this SO question comes up near the top when searching for role based routing in Rails.
I recently needed to implement something similar but wanted to avoid having a large number of conditionals in the controller - this was compounded by the fact that each of my user roles required completely different data to be loaded and presented. I opted to move the deciding logic to the routing layer by using a Routing Constraint.
# app/constraints/role_route_constraint.rb
class RoleRouteConstraint
def initialize(&block)
#block = block || lambda { |user| true }
end
def matches?(request)
user = current_user(request)
user.present? && #block.call(user)
end
def current_user(request)
User.find_by_id(request.session[:user_id])
end
end
The most important part of the above code is the matches? method which will determine whether or not the route will match. The method is passed the request object which contains various information about the request being made. In my case, I'm looking up the :user_id stored in the session cookie and using that to find the user making the request.
You can then use this constraint when defining your routes.
# config/routes.rb
Rails.application.routes.draw do
get 'home', to: 'administrators#home', constraints: RoleRouteConstraint.new { |user| user.admin? }
get 'home', to: 'instructors#home', constraints: RoleRouteConstraint.new { |user| user.instructor? }
get 'home', to: 'students#home', constraints: RoleRouteConstraint.new { |user| user.student? }
end
With the above in place, an administrator making a request to /home would be routed the home action of the AdministratorsController, an instructor making a request to /home would be routed to the home action of the InstructorsController, and a student making a request to /home would be routed to the home action of the StudentsController.
More Information
If you're looking for more information, I recently wrote about this approach on my blog.
You can't do this with routes because the routing system does not have the information required to make this decision. All Rails knows at this point of the request is what the parameters are and does not have access to anything in the database.
What you need is a controller method that can load whatever data is required, presumably the user record, and redirects accordingly using redirect_to.
This is a fairly standard thing to do.
Update:
To perform all of this within a single controller action you will need to split up your logic according to role. An example is:
class HomeController < ApplicationController
def home
case
when #user.student?
student_home
when #user.admin?
admin_home
when #user.instructor
instructor_home
else
# Unknown user type? Render error or use a default.
end
end
protected
def instructor_home
# ...
render(:template => 'instructor_home')
end
def student_home
# ...
render(:template => 'student_home')
end
def admin_home
# ...
render(:template => 'admin_home')
end
end
I am making a login route and added this to the routes.rb resources :sign_in
I made a controller like this:
class Mobile::Sign_inController < ApplicationController
layout "mobile/application"
def get
respond_to do |format|
format.html
end
end
def index
respond_to do |format|
format.html
end
end
end
and it seems to get routed correctly, but my view file which is located here:
/app/views/mobile/sign_in.html.haml
which just has 1 line for test purposes:
%strong{:class => "code", :id => "message"} Hello Signin!
But when I go to the url: http://m.cmply.local:8800/signin in the browser, the screen is totally white with nothing rendered in the browser.
Any idea why this happens and how to fix it?
Thanks!
A few problems here:
Your controller name should be SignInsController, not Sign_inController. Consider changing your name to UserSessionsController or similar, since that better reflects the resource it represents. You can still specify an alternate name for the URL (such as sign_in).
Why is your controller namespaced under Mobile? Your routes given don't reflect that, but you don't seem to have provided them all. The route should probably be under a scope:
scope :module => "mobile" do
resource :sign_in
end
Since there is only "one" sign in, it should have its route declared resource :sign_in, and probably even resource :sign_in, :only => [:new, :create, :destroy], depending on what you want. This means that the index action no longer exists, and you probably want to replace it with the new action`.
There is no get action by default for RESTful resources, I'm not sure what you meant it to be, but it should be something else.