Accessing specific pages in a controller/view on Rails App - ruby-on-rails

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.

Related

How to get Rails "new" path after failed form-input

I am implementing a simple language switcher in the common HTML menu-bar on a website in Rails, where the locale is path-based with the Rails-standard I18n implementation, e.g., /en/articles for Index for the Article model in English (in the following example, I consistently use pages for the model Article).
The language switcher is a simple text link (<a> in HTML) to the same page in another language. For example, the French page for /en/article/7 should be /fr/article/7.
Here is the most simplified code for the language-switcher link, which preserves all the GET query parameters for the current page:
str_link = link_to("fr", url_for(locale: "fr", params: request.query_parameters.except("locale")))
This code is included inside the common layout /app/views/layouts/application.html.erb so that it is applied to all pages on the website.
This works most of time but fails on the page after a user-input to a new page turns out to be invalid (status: :unprocessable_entity), according to the Rails standard CRUD action.
In the new page, the required link for the language switcher for French should be /fr/article/new; the above code-snippet works fine when a user freshly opens the new page in English (/en/article/new). However, once a user's input turns out to be invalid, the given URL is /en/articles, whose contents are equivalent to the new page.
How can I obtain the path /en/article/new in such cases so as to make the language switcher provide the correct link?
Obviously, when a user just requests the Index page /en/articles, the counterpart French page is /fr/articles, the URL of which is identical to the failed new page. So, they must be distinguished, that is, the path cannot be guessed thoroughly from the current URL and it depends on whether it is a fresh request or unprocessable_entity.
An answer to the question "Ruby on Rails Link to Previous Page on Form Failing After Invalid Input" suggests implementing hidden_field that contains the new page URL and the algorithm uses it. However, the suggestion does not work well in this case because the hidden_field for action create is POST and not a GET parameter. In the present case of the common language switcher, I need to deal with an arbitrary number of models inside the common layout (application.html.erb), meaning which parameter in params to permit cannot be pre-determined and hence to access hidden_field in params is tricky.
Here are the routes and Article Controller generated according to the Rails standard:
% bin/rails g scaffold Article title:string content:text
Routes:
# config/routes.rb
Rails.application.routes.draw do
filter :locale
resources :articles
end
Article Controller:
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def create
#article = Article.new(article_params)
respond_to do |format|
if #article.save
format.html { redirect_to article_url(#article), notice: "Article was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
private
def article_params
params.require(:article).permit(:title, :content)
end
end
I am using Rails 7.0. But I think the Rails version is almost irrelevant.
The language needs to be defined as a variable in your routes:
# config/routes.rb
scope "/:locale" do
resources :articles
end
This puts a language parameter in all your routes. Now you have to make sure that it's well-defined everywhere, as described here
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
end
And you need to define the default locale somewhere, e.g. in an intializer:
# config/initializers/locale.rb
Rails.application.config.i18n.default_locale = I18n.default_locale = :en

Creating a dynamic root route in rails

I have a rails app where I am wanting to make the landing page dynamic so that every time the user goes to it it changes.
I have cafe's in my app, and each cafe has its own show page. I want each show page to be dynamic. The url's are based off of the cafe's id, so I figured I'd have to use those IDs as the focal point for creating the dynamic work i'm looking for.
In my cafe's controller I have
class CafesController < ApplicationController
def root
array = Cafe.pluck(:id)
array.sample
end
end
in my routes file I have
root 'cafes#root'
The error I'm getting is
`CafesController#root is missing a template for this request format and variant. `
Would anyone know what I am missing with this one? Much appreciated.
I image you could do something like:
class CafeController < ApplicationController
def root
redirect_to Cafe.all.sample
end
end
By the way, this approach has the benefits of letting you keep and use all your normal routes in the conventional manner.
Also, you might consider calling this action something a little more descriptive. Perhaps something like random_cafe. IMO, root 'cafes#random_cafe' is a bit more understandable.
missing a template error because you have to specify a template to render, I'm imagining that array.sample will return something like /cafes/firstsampe.html.erb where firstsampe.html.erb persists inside cafes views folder so you can use it like this redirect_to :template => array.sample.
Hope it helps.
I don't have any idea about what your data looks like, but there are two approaches you can take here:
Show a random cafe when the user hits your home page (root)
Redirect a user to a cafe's page randomly when they hit the home page
In scenario 1:
Controller:
class CafesController < ApplicationController
def index
#cafe = Cafe.order("RANDOM()").first
end
end
Routing:
root 'cafes#index'
Views:
app/views/cafes/index.html.erb
<p><%= #cafe.name %></p>
In scenario 2:
Controller:
class CafesController < ApplicationController
def index
redirect_to cafe_path(Cafe.order("RANDOM()").first)
end
def show
#cafe = Cafe.find(params[:id])
end
end
Routing:
root 'cafes#index'
resources :cafes, only: [:show]
Views:
app/views/cafes/show.html.erb
<p><%= #cafe.name %></p>

Rails 4 - Couldn't find User without an ID

I'm new to rails, so any explanation & advise would much appreciated.
i have a webpage in which i would like any user to view that page not just the current_user, but i am unsure how to correctly define the instance variable #user in my controller
in my static_pages_controller.rb i have the below action recruiterpg
static_pages_controller.rb
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
in my controller, i have defined user as #user = User.find(params[:user_id]) but this breaks my code in the views; views/static_pages/recruiterpg.html.erb
when i define user as #user = current_user my code in the views works perfectly fine
what am trying to do is: for my views, the recruiterpg.html.erb, i would like
any user to be able to view the page not only the current_user to
view the page. Could one kindly advise me and explain to me how to
define #user correctly in my status_pages_controller.rb. i also
tried #user = User.find(params[:id]) but my code still breaks in the
views - i get the error message
Couldn't find User without an ID
You need to make sure you are passing a user_id to the recruiterpg action. For example, try this url in your browser (set user_id to a known id in the users table):
http://localhost:3000/dashboard?user_id=1
A suggested modification to your action:
def recruiterpg
#user = User.find params.require(:user_id)
#adverts = #user.adverts
#applications = #user.forms
end
If params[:user_id] isn't defined, you want to find a way to make visible what is being defined.
If you throw the following statements into your controller...
def recruiterpg
...
puts params
...
end
...you should see something like the following get spit out in your console when you load the page...
{"controller"=>"static_pages", "action"=>"recruiterpg", "id"=>"49"}
Take a look at the Rails guide for parameters. They can get defined in one of three ways.
One: As a query string similar to Sean's answer above.
Two: Routing parameters. See section 4.3 in the Rails guide. In your case, that would mean you should have something like the following in routes.rb:
get '/dashboard/:user_id' => 'staticpages#recruiterpg'
Note that there's nothing magic about :user_id in that string.
Three: From a form which it doesn't seem like applies here, since a user isn't submitting data.
Since you're new, here is some information for you:
User Story
Firstly, the best way to resolve errors is to identify your user story.
A "user story" is a software principle in which you put the "user's perspective" first -- explaining how the software should work in conditions defined from how the user engages with it.
One of the main issues you have with your question is your user story is very weak; it's hard to decifer what you're trying to achieve.
Next time you ask a question, you should try your hardest to describe how the user should see your app, before providing code snippets :)
Controller
Your main issue is an antipattern.
An antipattern is basically a "hack" which will likely break another part of your app in future. Kind of like duct tape for software):
#app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
end
So you're showing a static page but yet you want to populate it with data?
Hmm...
What you should be doing is something like the following:
#config/routes.rb
resources :users do
resources :recruiters, only: :index #-> url.com/users/:user_id/recruiters
end
#app/controllers/recruiters_controller.rb
class RecruitersController < ApplicationController
def index
#user = User.find params[:user_id]
#adverts = #user.adverts
#applications = #user.forms
end
end
This will allow you to populate the following view:
#app/views/recruiters/index.html.erb
<%= #adverts %>
--
It's important to note the structure of the controller / routes here.
The issue you have is that you're calling a "static page" and expecting to have params available to find a User. This can only happen if you have params available...
Params
Rails is not magic, and as such if you want to look up a user, you have to provide the parameters to do so.
This is why you're able to look up current_user -- the params are already set for this user.
As such, you'll need to use something called nested routes in order to attain a user ID other than that of current_user:
#config/routes.rb
resources :users do
resources :recruiters #-> url.com/users/:user_id/recruiters
end

Rerouting model contents using to_param

I am wanting to expand the URLs associated with the contents of a model called Product, at the moment, I can view a specific product by going to products/ID.
I would like to extend the product URL so it includes some more descriptive information, such as the product name.
I have previously been advised to adjust the to_param function (in Product.rb) as below:
def to_param
"#{id}-#{product_name.parameterize}"
end
However, this doesn't currently work. The URL associated with each product appears correctly when you hover over it / click it, but there is no matching product found. I get the error no match for ID=ID-specific-product-name
If i visit /products/id i can still successfully view the specific item
Can anyone guide me as to how I could generate this longer URL containing the product name (:product_name)?
EDIT
The show controller action in my controller is:
def show
#uniqueturbo = Uniqueturbo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #uniqueturbo }
end
end
If you're trying to make some SEO friendly urls
http://www.yourdomain.com/products/123123-My-Little-PonyBook
I think that the easiest way is to change the routes, like this
get '/products/:title/:id' => "products#show"
and then you'll get seo-friendly url's like:
http://www.yourdomain.com/products/My-Little-PonyBook/123123
To generate this url, create helper
def url_for_product(product)
"/products/#{product.title}/#{product.id}"
end
The other way is to leave the normal RESTful route, and reparse 'id' parameter, like:
def show
product_id = params[:id].split('_')[0] # :-)
# ...
end
and still you need the helper method, this time, sth like:
def url_for_product(product)
product_path(product) + "_#{product.title.tableize}"
end

switching rails controller

I have to separate models: nested sections and articles, section has_many articles.
Both have path attribute like aaa/bbb/ccc, for example:
movies # section
movies/popular # section
movies/popular/matrix # article
movies/popular/matrix-reloaded # article
...
movies/ratings # article
about # article
...
In routes I have:
map.path '*path', :controller => 'path', :action => 'show'
How to create show action like
def show
if section = Section.find_by_path!(params[:path])
# run SectionsController, :show
elsif article = Article.find_by_path!(params[:path])
# run ArticlesController, :show
else
raise ActiveRecord::RecordNotFound.new(:)
end
end
You should use Rack middleware to intercept the request and then rewrite the url for your proper Rails application. This way, your routes files remains very simple.
map.resources :section
map.resources :articles
In the middleware you look up the entity associated with the path and remap the url to the simple internal url, allowing Rails routing to dispatch to the correct controller and invoking the filter chain normally.
Update
Here's a simple walkthrough of adding this kind of functionality using a Rails Metal component and the code you provided. I suggest you look at simplifying how path segments are looked up since you're duplicating a lot of database-work with the current code.
$ script/generate metal path_rewriter
create app/metal
create app/metal/path_rewriter.rb
path_rewriter.rb
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class PathRewriter
def self.call(env)
path = env["PATH_INFO"]
new_path = path
if article = Article.find_by_path(path)
new_path = "/articles/#{article.id}"
elsif section = Section.find_by_path(path)
new_path = "/sections/#{section.id}"
end
env["REQUEST_PATH"] =
env["REQUEST_URI"] =
env["PATH_INFO"] = new_path
[404, {"Content-Type" => "text/html"}, [ ]]
end
end
For a good intro to using Metal and Rack in general, check out Ryan Bates' Railscast episode on Metal, and episode on Rack.
Rather than instantiating the other controllers I would just render a different template from PathController's show action depending on if the path matches a section or an article. i.e.
def show
if #section = Section.find_by_path!(params[:path])
render :template => 'section/show'
elsif #article = Article.find_by_path!(params[:path])
render :template => 'article/show'
else
# raise exception
end
end
The reason being that, whilst you could create instances of one controller within another, it wouldn't work the way you'd want. i.e. the second controller wouldn't have access to your params, session etc and then the calling controller wouldn't have access to instance variables and render requests made in the second controller.

Resources