How do I re-write this route to be shorter? - ruby-on-rails

I have resources :tags in my routes.rb.
So when I go to /tags/ios, it shows me the correct Tag#Show view.
What I would like to happen is when the user goes to /tags/iosit shows it as /ios and likewise I want that generated path to always be /ios (and not have the leading /tags).
Here is an example of how I am rendering that link, within an each block:
<%= link_to "#{tag.name}", url_for(tag) %>

In your routes.rb
resources :tags
match '/:tag_name' => 'tags#show'
Then in your tags_controller#show action you have access to the tag name via:
params[:tag_name]
Be sure to put that at the end of your routes file, as it will catch everything. Also, since it will catch everything you should render a 404 if it's not a valid tag name:
def show
unless #tag = Tag.where(name: params[:tag_name]).first
raise ActionController::RoutingError.new('Not Found')
end
end
To redirect /tags/ruby to /ruby:
match "/tags/:tag_name" => redirect("/%{tag_name}")
http://guides.rubyonrails.org/routing.html

well the problem here is namespace
if you define a route like
get "/(:slug)" => "Tags#show"
that will basically match anything where its a valid tag or not. The way I would do it would be
in your routes file:
begin
Tag.all.each do |t|
begin
get "#{t.slug}" => "Tags#show"
rescue
end
end
rescue
end
Then in your tags controller you can
def show
slug = request.env['PATH_INFO']
#tag = Tag.find_by_slug(slug)
end
My answer comes from another answer I wrote that you may be interested How to create app-wide slug routing for Rails app?

So even though I got lots of nice suggestions from these guys, I found what I think is the best solution for me.
All I am trying to do is basically make /tags/ruby be rendered as /ruby both in the actual URL on the links of all the tags, and in the URL bar.
I don't want to do anything that will add load to my app.
This is the solution that works for me:
resources :tags, path: "", except: [:index, :new, :create]
Using path:, you can specify what you want the path to display as. i.e. what do you want to appear before your resource. So if you wanted your URLs to look like myhotness/tags/ruby, then you would simply do path: "myhotness".
I didn't want anything in my path, so I just left it blank.
For what it's worth, you can even add a constraint to that route, like so:
resources :tags, path: "", except: [:index, :new, :create], constraints: { :id => /.*/ }

Related

Route Controller#show method like how Controller#index would in Rails

Hi guys I am new to rails. Sorry if I can't define this question properly.
What I wanted is for:
domain.com/posts/1-sample-post
to be routed like this:
domain.com/1-sample-post
How do I achieve this in rails routes? I've tried searching for this for almost 3 hours. This is very easy in PHP frameworks. I thought this is easy in Rails too.
I forgot to mention I have High_voltage gem installed in my app for my static pages.
Did this:
#routes.rb
resources :posts
get '/:id' => 'posts#show'
Now my High_voltage pages could not be rendered.
Update Solution:
So here is what we did in the routes:
Rails.application.routes.draw do
resources :authors
constraints(lambda { |req| Author.exists?(slug: req.params["id"]) }) do
get '/:id' => 'authors#show'
end
devise_for :users
resources :posts
constraints(lambda { |req| Post.exists?(slug: req.params["id"]) }) do
get '/:id' => 'posts#show'
end
end
Note that it is important to only use an exists? query here as it is very fast than other methods, so it won't eat that much loading time to render a record.
Special thanks to the guys below who helped a lot. Nathanvda, rwold, and Tai.
So the other answer correctly suggested something like
get '/:id', to: 'posts#show'
But this is a catch-all route and if there are no other routes defined this will catch all routes, also your HighVoltage, if it is configured to serve pages on root. You now have two catch-alls: one to find a static page and one to find a post.
Best solution in this case, imho is to make the static pages explicit (since I am assuming there will not be that many?)
get '/about' => 'high_voltage/pages#show', id: 'about'
get '/:id' => 'posts#show'
If you have a lot of pages, it seems easiest to just present the high-voltage on a different route? E.g. something like
get '/pages/:id' => 'high_voltage/pages#show'
get '/:id' => 'posts#show'
In both of these cases, since we use explicit routing, you would have to disable the default routing in the high-voltage initializer:
# config/initializers/high_voltage.rb
HighVoltage.configure do |config|
config.routes = false
end
[UPDATE: add special controller to consider both posts and pages]
Add a HomeController like this:
class HomeController < ApplicationController
# include the HighVoltage behaviour --which we will partly overwrite
include HighVoltage::StaticPage
def show
# try to find a post first
#post = Post.where(id: params[:id).first
if #post.present?
render 'posts/show'
else
# just do the high-voltage thing
render(
template: current_page,
locals: { current_page: current_page },
)
end
end
end
Of course I did not test this code, but I think this should get you started. Instead of doing the rendering of the post, you could also redirect to the posts-controller which is maybe easier (and you will use the PostsController fully) but adds a redirect and will change the url.
In your routing you will then have to write
get '/:id', 'home#show'
In your routes.rb file:
get '/:id-sample-post', to: 'posts#show', as: :sample_post
assuming that posts is your controller and show is the action that calls the view for your article with the given id.
EDIT AFTER OP COMMENT:
The as: :sample_post clause should create a helper sample_post_path that can be invoked as <%= link_to "Show", sample_post %>.

Rails controller using wrong method

I'm working on a Rails project that is giving me some problems. I've got a controller characters_controller.rb that has two methods.
class CharactersController < ApplicationController
before_action :authenticate_player!
def view
#character = Character.find(params[:id])
unless #character.player_id == current_player.id
redirect_to :root
end
end
def new
end
end
I've got routes set up for each of those.
get 'characters/:id', to: 'characters#view'
get 'characters/new', to: 'characters#new'
The first route works fine. I can get go to /characters/1 and I'm shown the appropriate view and the requested information. If I visit /characters/new I'm shown an error that references characters#view.
raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
and
app/controllers/characters_controller.rb:6:in `view'
So /characters/new is trying to get a Character from the database with an id of "new" but that doesn't work well. Any idea what I may be doing wrong?
Order matters in routes.rb, the router will find the first route that matches.
In your case, it would never go to characters#new, because the line above it will always match.
A simple solution would be to swap the two lines.
A better solution might be to use resource routing as documented in the Rails routing guide.
Rails parses routes sequentially and therefore it is considering 'new' as the :id for characters/:id route (which encountered first).
Just swap the order of routes as follow:
get 'characters/new', to: 'characters#new'
get 'characters/:id', to: 'characters#view'
If using this order in your routes.rb, for /character/new request, rails will understand that request is handled by view action with paramas[:id] = 'new'
Let characters/new before the other will resolve your problem:
get 'characters/new', to: 'characters#new'
get 'characters/:id', to: 'characters#view'
Try to use resourceful routes, much cleaner:
resources :characters, only: [:new, :show]
Also I suggest rename def view to def show to follow rails convention
Just use
resources :characters, :path => "characters"
in your routes.rb

Rename path without renaming resources (models etc)

I have a big feature called 'press releases'.
Client wants URL to say /updates rather than /press_releases.
Basic CRUD.
Is it possible to change the URL without renaming everything?
For example, when I say resources_path - is it possible to make it generate link /updates ?
Routes look like this:
resources :press_releases, except: :new do
collection do
post 'sort'
end
end
You can use a combination of options to do this.
The controller option allows you to specify a controller to use for the routes, and the as option allows you to rename the helpers.
So, something like this should work:
resources :updates, controller: 'press_releases', as: 'press_releases', except: :new do
collection do
post 'sort'
end
end
Sure,
resources :press_releases, :path => "/updates", except: :new do
collection do
post 'sort'
end
end

How to declare a rails resource with a parameter for new action?

I have a model named Entree for which the new action needs a parameter, the id of another model named Cave. I don't want to nest Entree in Cave since Cave is already nested.
What I did was declaring the resource Entree as follow in routes.rb:
resources :entrees, :except => [:new]
match "/entrees/new/:id", :to => "Entrees#new", :as => 'new_entree'
That works, but the problem is when there's an error in the create action, I want to display the page again with the invalid input. But since there's no new action, I must do a redirect_to new_entree_path, which does not keep the user input.
I have tried the following (simplest) route:
resources :entrees
But then the path http://localhost:3000/entrees/new/32 returns an error:
No route matches [GET] "/entrees/new/32"
The question is, how can I declare the Entree resource in the routes file with a parameter for the new action ?
I'm not sure if that's a hack or not, but the following works and seems cleaner than 2-levels nesting.
resources :entrees, :except => [:new] do
collection do
get 'new/:id', :to => "entrees#new", :as => 'new'
end
end
Now I can do a render "new" instead of a redirect_to.
I must say that I must have asked my question wrongly, my bad.
Rails has a route helper called path_names that does this:
resources :entrees, path_names: { new: 'new/:id' }
To improve gwik 's solution (which in fact didn't work for me):
resources :things, except: [:new] do
new do
get ':param', to: 'things#new', as: ''
end
end
It gets you new_thing_* helpers (instead of new_things_*) for free.
If you want to use Rails resource routes, you will have to nested them according to how they work
resources :caves do
resources :entrees
end
to get the route /caves/70/entrees/new
Otherwise, you are in a world of creating manual match routes.
match "/entrees/new/:id", :to => "entrees#new", :as => 'new_entrees'
I do not understand why you are forced to use a redirect? The new_entrees route is valid. You will not be able to use the form_for helper, since it is not a resource, but the form_tag helper will work.
UPDATE: Render and Route
The Route does not directly change what view is rendered in the Controller. That is determined by the controller itself. Render examples:
render :new will render the new action's view
render 'entrees/new' will render the entrees/new template
I found this generates the correct new_thing_path method not new_things_path as Antoine's solution.
resources :things, :except => [:new] do
with_scope_level(:new) do
get 'new/:param', :to => "things#new", :as => ''
end
end

Rails Controllers => params[]

Can someone please help me understand params in nested attributes a little better?
I am using Apotomo. But for the example. We could just assume its in the ApplicationController
I have a simple controller show action.
if params[:id].present?
#menu = Menu.find(params[:id])
else
#menu = Menu.first
end
Which checks to see if a menu id is specified in the URL. If not, it shows the first menu.
This works well as long as I'm only on the /menus/ URL.
But I have nested attributes. So once we visit URL /menus/17/categories/
It finds params[:id] as that of the category, not the menu.
Once I'm nested, I can call :menu_id, and it works fine. But no longer works on the parent object.
How do I look for params[:id] of the menu object regardless of where I am in the URL?
And am I missing something completely?
Here is my routs config as well:
resources :menus, :only => [:show, :home] do
resources :categories, :only => [:index, :show]
end
Thanks for your patience.
I would check how routing is defined. Maybe there is a reason why this link is translated this way.

Resources