creating a widget in rails with key validation - ruby-on-rails

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.

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

The dreaded missing template error

I've read everything I can find, and I'm still stumped.
I'm trying to use a before_filter to catch users who are not logged in, and send them to an alternative index page. The idea is that users who are logged in will see a listing of their own articles when they hit index.html.erb, users who are not logged in will be redirect to a showall.html.erb page that lists the articles but does not let them read them (and hits them with some ads).
I added a route:
resources :articles do
get "showall"
resources :comments
end
'Rake routes' shows a route article_showall. I have a partial _showall.html.erb in the views/articles folder (it only contains the text 'showall is working!'). If I render showall in another view (<%= render "showall" %>), it works fine.
This is the applicable part of my controller:
class ArticlesController < ApplicationController
skip_before_action :authorize, only: [:index, :show, :showall]
before_filter :require_user, :only => [:index]
def require_user
unless User.find_by(id: session[:user_id])
render 'showall', :notice => "Please log in to read articles."
end
end
def index
#articles = current_user.articles
end
def showall
#articles = Article.all
end
When I run it (while not logged in), it get the following error:
Missing template articles/showall, application/showall with....etc
I'm stumped. Why can I render 'showall' in a view, but I get Missing Template error when I refer to it in my controller? Thank you in advance for any help!
David and user4703663 are right. Your problem is that you named the template to be a partial but are rendering it as a template. You could either remove the underscore from the file name and leave your code as it is, or leave the filename as it is and "render partial: 'showall'" from your index view.
edit: I should add that the missing template error should not instill dread in your heart. It's guiding you to your mistake, directly. Don't stress it and just try to remember what it meant next time. It's almost always a typo, or loading a partial but naming it as a template or vice versa, or forgetting a subfolder for a relative path or something like that. It's normal, and the interpreter spits those errors to help you, not to oppress you. 😄
Replace
render 'showall', :notice => "Please log in to read articles."
with
render :file => '/partial/showall', :notice => "Please log in to read articles."
Great, fixed that problem, and now it spits back
undefined method `each' for nil:NilClass
even though the 'showall' is nearly identical to 'index'.
SIGH

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

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

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.

Resources