Routing to static pages in subfolders - ruby-on-rails

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 %>

Related

Rails Routing Incorrectly links to show.html

I'm learning Rails and have encountered some behavior I don't understand. I'm trying to create a simple CRUD app. On the 'new' view after data entry fields and submit button I am trying to add a link that will go to another page (I'll call it 'fnord').
Instead of linking to fnord, it's going to show.html. Given that rails is convention based I wonder if this is a default behavior of some kind, but I haven't been able to figure out what triggers it or the proper way of routing to fnord.
This is Rails 3.2.21, Ruby 1.9.3. I originally generated the model, views and controller using scaffold and then started tweaking things.
Here's my controller (minus unrelated actions - index, destroy, etc):
class EmployeesController < ApplicationController
def show
#employee = Employee.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #employee }
end
end
def new
#employee = Employee.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #employee }
end
end
def fnord
#nothing yet - just trying to get the page to appear right now
end
Here's the relevant code from new.html.erb:
<h2>New Employee</h2>
<%= render 'form' %>
<!-- content, data entry fields, submit button, etc -->
<div>
<%= link_to 'Fnord', employees_fnord_path %>
<div/>
Here's the view, fnord.hmtl.erb:
<h2>Fnord</h2>
<br/>
<p>this is fnord.html</p>
Here's my routes.rb:
MyApp::Application.routes.draw do
resources :employees
get 'employees/fnord'
root :to => 'employees#new'
end
I ran 'rake routes' and get the following output:
employees GET /employees(.:format) employees#index
POST /employees(.:format) employees#create
new_employee GET /employees/new(.:format) employees#new
edit_employee GET /employees/:id/edit(.:format) employees#edit
employee GET /employees/:id(.:format) employees#show
PUT /employees/:id(.:format) employees#update
DELETE /employees/:id(.:format) employees#destroy
employees_fnord GET /employees/fnord(.:format) employees#fnord
root / employees#new
As far as I can see this all looks right, but when I click on the link in new.html it returns show.html, not fnord.html.
The url that shows up in the browser is even locahost:3000/employees/fnord, but the content is from show.html.
I read the routing documentation (http://guides.rubyonrails.org/routing.html) but didn't see any explanations.
I've tried restarting rails (many times) and clearing my browser cache as well and those steps didn't help.
Can anyone enlighten me as to what I'm missing? Thank you very much.
The route for /employees/fnord matches the show route because the show route is looking for URL's in the format: /employees/:id
When you go to /employees/fnord it interprets fnord as the ID of some employee, and it routes it to the show method.
One way to fix it is to declare the /employees/fnord route before the resources routes, so that it matches first:
MyApp::Application.routes.draw do
get 'employees/fnord'
resources :employees
root :to => 'employees#new'
end
Another way is to simply change the name of the URL so that it doesn't match the pattern of /employees/:id:
MyApp::Application.routes.draw do
resources :employees
get 'employees1/fnord', to: 'employees#fnord'
# or whatever
get 'foo', to: 'employees#fnord'
# etc...
get 'employees/foo/fnord', to: 'employees#fnord'
root :to => 'employees#new'
end

preview page signup redirecting to another static page error

I have a preview page up with a form that takes in emails(#premails). I've created a model & migration for this.
I have a pages controller with a Home, About & Contact actions and corresponding views.
After they submit their email on the Home page, I want to redirect them to a static About page. I have not been able to achieve this.
this is my pages controller:
class PagesController < ApplicationController
def home
#premail = Premail.new
if #premail.save
redirect_to about_path
else
render home_path
end
end
def about
end
end
But when I open my localhost with this code I get:
NameError in PagesController#home
undefined local variable or method `about_path' for #<PagesController:0x337ac40>
How can I make this happen?
For your case, use:
if #premail.save
redirect_to :action => :about
end
else is not needed here, since by default Rails would render app/views/pages/home.html.erb, be sure you have this file.
Also when you redirect to about, you will need app/views/pages/about.html.erb file to be present.
Update
Seems you don't have this route in config/routes.rb, for Rails 3.x:
match ':controller(/:action(/:id))'
In Rails 4:
match ':controller(/:action(/:id))', :via => [:get , :post]
If you are planning to just answer to get, i.e. there are nor forms posting to controllers:
get ':controller(/:action(/:id))'
This will detect routes like localhost:3000/asd/qwe/1 and:
Use asd as controller AsdController
Use qwe as action:
class AsdController
def qwe; end
params[:id] would be equal to 1.
() means optional, for example if you go in your browser to localhost:3000/asd, Rails would call Asd#index, i.e.:
class AsdController
def index
# whatever you have here
end

Rails use nesting and resource path with other model

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.

Accessing specific pages in a controller/view on Rails App

I am trying to set individual Meta Descriptions and Titles to individual pages in a Ruby on Rails App. It was a previous developers App, that I have been given the task to edit. Also, I am new to Rails and Ruby.
The app has a controllers/pages_controller.rb where I was am able to set unique variables for #descriptionX and #title on some pages (mission and disclaimer), but not for others, such as pet_planning.
class PagesController < ApplicationController
def index
#title = params[:page].humanize
render params[:page]
end
def pet_planning
#descriptionX = 'pet planning'
#title = 'pet planning title'
render :pet_planning
end
def mission
#title = 'Our Mission Statement'
#descriptionX = 'Mission Description'
render :mission
end
def disclaimer
#title = 'Our Disclaimer'
render :disclaimer
end
end
I think that the render params[:page] is where I am getting lost. I'm not 100% sure of what this is doing, or how to use it.
I don't understand why I would be able to control the #title and #description of mission but not pet_planning when their views are both in the same /views/pages/ directory. And I can't seem to find any distinction between the two anywhere else in the app.
Also, tried to add = #title = 'Pet Planning' in the /views/pages/pet_planning.html.haml file. It does change the title, however it also displays at the top of the page content unexpectedly.
Any help would be appreciate. Thanks.
I'd recommend having a read of the ActionController guide, which explains how Rails turns a request from the user into a page to render.
Basically, when you send a request, for example
GET http://www.example.com/pages/?page=pet_planning
then Rails works out what to do with it using the router (the routing guide explains this in more detail). I would imagine that your app is set up so that the /pages route matches to the PagesController#index action. You can have a look in your config/routes.rb file and/or type rake routes at the terminal to see how your routes are set up.
The bit after the question mark in the request is the "query string", and Rails turns this into a params hash which, for my example above, would look like {:page => "pet_planning"}. Your index action looks at that to get the name of the page to render - that's what render params[:page] is doing.
I guess that the reason you can modify the variables for some of your pages and not others is that some of them have their own routes - /pages/mission uses the PagesController#mission action, for example - while certain pages are accessed via the index action using the page param - /pages/?page=pet_planning or possibly /pages/index.html?page=pet_planning.
Update: Your existing route
match 'practice_areas/:page' => 'pages#index', :as => :pages
could be broken up into
match 'practice_areas/pet_planning' => 'pages#pet_planning' :as => :pet_planning
# etc ...
which would correspond to a controller that looks like this
class PagesController < ApplicationController
def pet_planning
#title = "Pet planning!"
#description = "Whatever..."
end
end
Your suggestion is close, but because the route format is "controller_name#action_name", you would require multiple controllers that looked like this
class PetPlanningController < ApplicationController
def index
#title = "Pet planning!"
#description = "..."
end
end
and you would have to move your views from app/views/pages/pet_planning.html.haml to app/views/pet_planning/index.html.haml. So it's probably not quite what you want.
Note that there might be a better way to tackle the problem than splitting everything up into separate actions, if all you are doing differently in each one is customising the title and description. For example, you could use a hash that maps your page name to its corresponding information:
class PagesController < ApplicationController
PAGE_INFO = {
"pet_planning" => ["Pet planning!", "Whatever..."],
"other_page" => ["Title", "Description"],
# ...
}
def index
page_name = params[:page]
#title, #description = PAGE_INFO[page_name]
render page_name
end
end
The render calls in pet_planning, mission, and disclaimer do the same as default behavior, so those calls can be removed. They are telling rails to use the pages with the given file names. For the index method, this is rendering a page based on a parameter.
The title and description are likely set in the layout. Look in /views/layouts/application.html.haml or /views/layouts/pages.html.haml.

Best practices for static pages in rails app

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.

Resources