Ok so I have a rails app where user can create pins. Then they can comments those pins. What I what to do is to remove the controller name in pin url.
So instead of: http://localhost:3000/pins/name I have http://localhost:3000/name
**I did that by using this in my **config/routes.rb****
Rails.application.routes.draw do
resources :pins, :only => [:index, :new, :create], :path => '' do
resources :comments
member do
put 'upvote'
end
end
But now, when I try to comment a pin I have this error:
wrong constant name 'pin name'
and the error come fom this lines from my comments_controller.rb:
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.friendly.find(id)
end
Any ideas how I could fix this ?
EDIT:
**rake routes** output:
pin_comments GET /:pin_id/comments(.:format) comments#index
POST /:pin_id/comments(.:format) comments#create
new_pin_comment GET /:pin_id/comments/new(.:format) comments#new
edit_pin_comment GET /:pin_id/comments/:id/edit(.:format) comments#edit
pin_comment GET /:pin_id/comments/:id(.:format) comments#show
PATCH /:pin_id/comments/:id(.:format) comments#update
PUT /:pin_id/comments/:id(.:format) comments#update
DELETE /:pin_id/comments/:id(.:format) comments#destroy
Use params (Hash with request parameters) instead of resource, id = request.path.split('/')[1, 2]. This should fix your second problem.
I think you probably came from a php background or something like that, cause I used to think like that back then when I used php my self, but in rails you don't touch the URI or try to parse it or anything, that's the router's job, and if it reached that part of your code then the job was already done.
If you are using the pin's name as a url then you should either use a friendly_id gem, or set the to_param method of the model.
The id of the pin will always be in the params[:pin_id] because that's how it's named in the routes, and the id of the comment will be in the params[:id], mind the variable names in the route
/:pin_id/comments/:id
I'm not sure what you mean by the resource, but if you mean the model name, well you're in a pin's controller, so it's safe to assume it's a pin model, but if you want the name of the controller then you could access params[:controller]
Your load_commentable method would probably look like this after fixing everything
def load_commentable
#commentable = Pin.find(params[:pin_id]).comments.where(id: params[:id])
end
Related
I am trying to understand how does rails know the difference between the two routes
GET /users/:id
and
GET /users/new
when we type
resources :users
I tried to trace and understand the resources method in rails source code but I didn't understand it completely.
def resources(*resources, &block)
options = resources.extract_options!.dup
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
with_scope_level(:resources) do
options = apply_action_options options
resource_scope(Resource.new(resources.pop, api_only?, #scope[:shallow], options)) do
yield if block_given?
concerns(options[:concerns]) if options[:concerns]
collection do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
end
new do
get :new
end if parent_resource.actions.include?(:new)
set_member_mappings_for_resource
end
end
self
end
Is the below piece of code does that?
new do
get :new
end if parent_resource.actions.include?(:new)
if yes, can you explain?
Also if I tried to write another route with the same GET users/new format it redirects to GET users/:id, so how can I write another route like GET users/whatever without considering whatever as the :id?
Below is example of routes.rb
Example 1:
get '/feedbacks/:id' => 'feedbacks#show'
get '/feedbacks/count' => 'feedbacks#count'
feedbacks/count redirects to /feedbacks/:id
Example 2:
resources :feedbacks
get '/feedbacks/count' => 'feedbacks#count'
feedbacks/count redirects to /feedbacks/:id
Rails does not differentiate, it's just searches for the first record, which is suitable for the conditions
That's why new is generated earlier than id
Example 1:
get '/feedbacks/count' => 'feedbacks#count'
get '/feedbacks/:id' => 'feedbacks#show'
Example 2:
resources :feedbacks do
member do
get '/feedbacks/count' => 'feedbacks#count'
end
end
You can read about it here
Rails routes are just basically regular expressions on steriods. They match a path expression, a HTTP method and any additional constraints.
Routes have priority in the order they are defined. When the router matches a request in the routes collection it stops searching farther which is why the routes at the top always win.
If you look at the output from the resources macro your can see that the routes are ordered to take this into consideration:
Prefix Verb URI Pattern Controller#Action
things GET /things(.:format) things#index
POST /things(.:format) things#create
new_thing GET /things/new(.:format) things#new
edit_thing GET /things/:id/edit(.:format) things#edit
thing GET /things/:id(.:format) things#show
PATCH /things/:id(.:format) things#update
PUT /things/:id(.:format) things#update
DELETE /things/:id(.:format) things#destroy
The GET /things/new route must be declared before GET /things/:id. Otherwise it would be matched by the things#show route and give an ActiveRecord:: RecordNotFound error as the controller would attempt to find Thing id = "new".
users/:id, so how can I write another route like GET users/whatever
without considering whatever as the :id?
Use the collection option:
resources :things do
get :foo, on: :collection
end
# same but with block syntax
resources :things do
collection do
get :foo
get :bar
end
end
You can also add additional member routes (prefixed with an id):
resources :trips do
patch :cancel
end
Note that Rails defaults to on: :member so you don't need to explicitly set it.
Since resources yields at the top (yield if block_given?) these routes will have the correct priority.
See:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
I'm a new user of rails so it's complicated to understand how the routes.rb works! So I try to modify a route, I got a path that look like this:
user/:id/edit but i want that the id not appear in the path.
I try to use this method :
get '/users/:id/edit', to: 'users#edit', as: 'users/edit'
but it changes nothing. In my routes.rb i got :
resources :users, only: [:create, :new, :show, :edit]
Someone know how to do this? I already take a look at this guide
If you already take a look at guides, do you read about singular resources?
Sometimes, you have a resource that clients always look up without
referencing an ID. For example, you would like /profile to always show
the profile of the currently logged in user. In this case, you can use
a singular resource to map /profile (rather than /profile/:id) to the
show action:
resource :geocoder
creates six different routes in your application, all mapping to the Geocoders controller:
GET /geocoder/new geocoders#new return an HTML form for creating the geocoder
POST /geocoder geocoders#create create the new geocoder
GET /geocoder geocoders#show display the one and only geocoder resource
GET /geocoder/edit geocoders#edit return an HTML form for editing the geocoder
PATCH/PUT /geocoder geocoders#update update the one and only geocoder resource
DELETE /geocoder geocoders#destroy delete the geocoder resource
If you have taken,
resources :users
Now change this route as follows,
get '/users/edit', to: 'users#edit', as: 'users_edit'
Now in your view file where you have edit link, change the link to,
<%= link_to 'Edit', users_edit_path(:id => user.id) %>
Now this links to the edit action of users controller with an id parameter.
Now, in the users controller file,
class UsersController < ApplicationController
def edit
// params[:id] will be the ID that you sent through the view file.
#user = User.find(params[:id])
end
end
Thats it, you are done with your custom route, now the route will be users/edit instead of users/:id/edit
I am trying to set up routes for the "Page" model nested within the "User" model. Only "Pages" needs to be resourcesful. (User has_many pages)
This is the original way I did it:
resources :users, path: '', only: [] do
resources :pages, path: ''
end
The above worked for me just fine. The routes I got were these:
user_pages GET /:user_id(.:format) pages#index
POST /:user_id(.:format) pages#create
new_user_pages GET /:user_id/new(.:format) pages#new
edit_user_pages GET /:user_id/:id/edit(.:format) pages#edit
user_pages GET /:user_id/:id(.:format) pages#show
PUT /:user_id/:id(.:format) pages#update
DELETE /:user_id/:id(.:format) pages#destroy
This made sense for me because /john-doe/page1 would be user_pages_path(#user, #user.pages.first).
However, new_user_pages didn't make sense because a user can only make a page for himself/herself. Therefore, each user should visit /new, not "/:user_id/new". Furthermore, what happens if the user visits another user's ":another_user_id/new" ? (it would make more sense to do new_pages_path and '/new' instead of new_user_pages_path and /:user_id/new).
Another way I tried to do the above routing:
I also realized the above can be accomplished in a shorter way due to the fact that ":users" does not need to be resourceful:
resources :pages, path => :user_id
However, this resulted in paths w/o "user" in them:
pages GET /:user_id(.:format) pages#index
POST /:user_id(.:format) pages#create
new_pages GET /:user_id/new(.:format) pages#new
edit_pages GET /:user_id/:id/edit(.:format) pages#edit
pages GET /:user_id/:id(.:format) pages#show
PUT /:user_id/:id(.:format) pages#update
DELETE /:user_id/:id(.:format) pages#destroy
What is the "rails" way of doing this? Also, should I remove "new" from the resource and define it separately?
Also, does it make sense to use scope or namespace instead?
Thanks,
Nick
Why don't you want your routes to follow the new_user_pages_path format? There might be reasons for doing it the way you want that I'm not aware of, but for me, it's a lot easier when all of my nested resource paths follow the same format.
To answer your question about preventing a user from visiting /:another_user_id/new, you can prevent this by adding a before_filter to make sure that the id getting passed to the new action matches the id of the user that is currently logged in.
This is what I'm using in the app I'm building currently:
things_controller.rb
class VendorsController < ApplicationController
#other filters
before_filter :correct_user
#controller methods
private
def correct_user
if params[:user_id]
#user = User.find(params[:user_id])
unless #user && current_user == #user
redirect_to(root_path)
end
else
redirect_to(root_path)
end
end
end
Of course, you'll need a current_user method for this to work (mine comes from devise).
I have a routes folder
resources :groups
In my GroupsController here are some actions
def new
#group = Group.new
end
def update
end
def show
#groups = Group.find(params[:id])
end
When I run rake routes I see
new_group GET /groups/new(.:format) groups#new
So now in my html page I have
a.btn.btn-primary href="/groups/new"
The funny thing is whenever I click on the link, it tells me
No route matches {:action=>"show", :controller=>"groups"}
I actually do have a route match for the show action. I also checked and do not have any filters that are redirecting me to the show action in my GroupsController. I have no idea why it's redirecting me to show. What's wrong and how do I fix this? I must be missing something obvious.
UPDATE
If I go and remove the show action from the resources method like so
resources :groups, except: [:show]
Then it tells me that it could not find an update action.
SOLVED
I found out in my new.html page there was a call to group_path, when in reality it should groups_path
I found out in my new.html page there was a call to group_path, when in reality it should groups_path.
Lets say I am creating a new Foo using a form and a standard Rails restful controller, which looks something like this:
class FoosController < ApplicationController
...
def index
#foos = Foo.all
end
def new
#foo = Foo.new
end
def create
#foo = Foo.create(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
render 'new'
end
end
...
end
So, if I use the standard restful controller (as above), then when I'm creating the Foo I am at example.com/foos/new, and if I submit the form and it saves correctly I'm at example.com/foos showing the index action. However, if the form is not filled correctly the form is rendered again and error messages are shown. This is all plain vanilla.
However, if errors are shown, the form page will be rendered but the URL will be example.com/foos, because the CREATE action posts to that url. However, one would expect to find Foos#index at example.com/foos, not the form they just submitted now with error messages added.
This seems to be Rails standard behavior, but it doesn't make a lot of sense to me. Obviously I could redirect back to new instead of rendering new from the create action, but the problem with that is the error messages etc. would be lost along with the partially complete Foos in memory.
Is there a clean solution for this problem, a way to send people back to example.com/foos/new when there are errors in the new Foo form they submitted?
Thanks!
To answer your comment on another answer:
I'm wondering if there's a way, without rewriting the controller at all, to tell rails that you want the URL to match the rendered template, rather than the controller action that called it.
I don't think so; URLs are tied directly to routing, which is tied into a controller and action pair--the rendering layer doesn't touch it at all.
To answer your original question, here's information from another similar question I answered.
As you've found, by default when you specify resources :things, the POST path for creating a new thing is at /things. Here's the output for rake routes:
things GET /things(.:format) {:action=>"index", :controller=>"things"}
POST /things(.:format) {:action=>"create", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
It sounds like you want something more like this:
create_things POST /things/new(.:format) {:action=>"create", :controller=>"things"}
things GET /things(.:format) {:action=>"index", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
Although not recommended, you can get this result with the following route:
resources :things, :except => [ :create ] do
post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
end
You would also need to modify your forms to make them POST to the correct path.
You could hook into rails routing by adding this in an initializer:
https://gist.github.com/903411
Then just put the regular resources in your routes.rb:
resources :users
It should create the routes and behaviour you are looking for.
You can set up the routing manually, if you're that concerned about what URL is going to show. For what you want, you can have a GET to /foos/new render your form, and a POST to the same URL do the creation:
map.with_options :controller => :foos do |foo|
foo.new_foo '/foos/new', :conditions => {:method => :get}, :action => :new
foo.create_foo '/foos/new', :conditions => {:method => :post}, :action => :create
foo.foos '/foos', :conditions => {:method => :get}, :action => :index
end
This should work without requiring any changes to your controller (yay!) - all three actions from your example are taken care of. The few disclaimers:
This is based on my routing for a 2.3.8 app - some syntax (semantics?) changes are probably required to get it into Rails 3 routing style.
My attempts to mix this style of routing with map.resources have failed horribly - unless you're more familiar with this than me, or Rails 3 routing is better (both easily possible), you'll have to do this for every route to the controller.
And finally, don't forget to add /:id, (.:format), etc. to the routes that need them (none in this example, but see #2).
Hope this helps!
Edit: One last thing - you'll need to hard-code the URL in your form_for helper on /foos/new.html.erb. Just add :url => create_foo_path, so Rails doesn't try to post to /foos, which it will by default (there might be a way to change the creation URL in the model, but I don't know of it, if there is one).
You could use Rack::Flash to store the parameters you wanted in the user's session and then redirect to your form url.
def create
#foo = Foo.new(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
flash[:foo] = params[:foo]
flash[:errors] = #foo.errors
redirect_to new_foo_path #sorry - can't remember the Rails convention for this route
end
end
def new
# in your view, output the contents of flash[:foo]
#foo = Foo.new(flash[:foo])
end