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
Related
Using Devise I would like to display User account information such as profile name, first & last name etc on another page called profile page within my rails application.
I have created a controller called profiles with a view called profile/show
In the controller have added the below code
def show
#user = User.find_by_profile_name(params[:id])
if #user
render action: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
end
end
In the view profiles/show I have the following code
<%= #user.profile_name %>
and the route is get 'profiles/show'.
My issue is when I do all of the above the profile name of the user still does not display on the profile page? There are no errors that come up it just doesn't display. I am not sure what code I am missing. I have checked the console and the user does have a profile name save to that ID and this is also in the devise account settings. So I am not sure how to get this information to display?
In Rails you would usually set it up like follow to take leverage of convention over configuration:
# config/routes.rb
resources :users, only: [:show, :index]
# app/models/user.rb
class User < ActiveRecord::Base
# ...
def self.find_by_uid!(uid)
User.find_by!("profile_name = :p OR id = :p", p: uid)
end
end
# app/controllers/users_controller.rb
class UsersController
# GET /users/:id
def show
#user = User.find_by_uid!(params[:id])
# Rails does the magic.
end
# GET /users
def index
#users = User.all
end
end
<%- # app/views/users/show.html.erb -%>
<h1><%= #user.profile_name %></h1>
The only special part here is that in the user model we create a class method which will query by id or profile_name. The reason that this is important is that it lets you use link_to(#user) and redirect_to(#user) as expected.
Which is also why we use resources :users. When the route name and the model line up the Rails polymorphic route handlers are able to do their job. If you want to use /profiles thats fine but never /profiles/show - including the action in the route defeats the whole purpose of REST.
The show action will render users/show.html.erb by default. So you rarely need to explicitly render in your controller.
render action: :foo
is only used when you want to render a template with the same name as another action, its usually used as follows:
def create
#something = Something.new
if #something.save
redirect_to(#something)
else
render action: :new # renders views/something/new.html.erb
end
end
If you want to explicitly render a template you would do render :foo or render "foo/bar".
And when you use find or find_by! it will raise an exception if the record is not found which by default will render the static 404 template. Reproducing this error handling in your actions is not very desirable since it violates the DRY pinciple.
When a user makes a request to the url /mobile in my Rails app, I would like a parameter to automatically be appended to the URL that gets loaded after the request (something like /mobile?tree_width=5)
I have tried a few things, all of which have not worked.
The closest I have gotten is this in my controller:
def mobile
respond_to do |format|
format.html{
# pass tree width here
render mobile_project_steps_path(#project, :tree_width => #project.tree_width)
}
end
end
I am getting the error
Missing template /projects/8/steps/mobile?tree_width=5
But I think this path should exist according to my rake routes:
mobile_project_steps GET /projects/:project_id/steps/mobile(.:format) steps#mobile
How do I add a param to the URL from a controller?
You need to check if the param is missing and if it is redirect to current action with extra param. I would squeeze it with in before_action:
before_action :check_tree_width, only: :mobile
def mobile
# Your actual logic
end
private
def check_tree_width
redirect_to(tree_width: #project.tree_width) unless params[:tree_width].present?
end
I've started coding up a widget controller so that I can have my users share their categories and the widget would display articles in that category; I've followed a few available resources but nothing specific to what I need.
Right now I'm just trying to get my WidgetController to work and validate if a valid key is enter or not; if so then display the view, if not render 'something' as a text; however I'm getting no where and getting error such as no action or no route matches I'm hoping someone can help and answer this question to me. So far I've got the following;
# Controller Widget
class WidgetController < ApplicationController
layout false
session :off
before_filter :validate_key, :only => [:category_bin]
def category_bin
#list articles but for now render text
render 'articles'
end
protected
def validate_key
if params[:key] == '01010101'
return
else
render 'not valid'
end
end
end
#Routes
SomeApp::Application.routes.draw do
match '/:controller/:action/:key'
end
#Browser
In the browser I visit the url like my iFrame would; http://localhost:3000/widget/category_bin/01010101 (this would render success and render my `articles` text ), http://localhost:3000/widget/category_bin/01010102 (would render `not valid` text and display nothing but that)
The wildcard route isn't very useful, try creating an explicit route like
get '/widget/category_bin/:key', to: 'widget#category_bin'
P.S - as a convention, controller names are usually plural, i.e widgets not widget.
P.P.S - rake routes from the command line is a really useful tool to see what rails thinks your routes are
P.P.P.S you may need to call return explicitly from your before filter, otherwise it will render 'not valid' (should be render plain: "not valid" btw) and then continue to render articles.
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
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.