REST Routes and overriding :id with to_param - ruby-on-rails-3

My controller is using the default RESTful routes for creating, adding, editing etc
I want to change the default :id to use :guuid. So what I did was:
# routes.rb
resources :posts
# Post Model
class Post < ActiveRecord::Base
def to_param # overridden
guuid
end
end
This works but my modifed REST controller code has something like this
def show
#post = Post.find_by_guuid(params[:id])
#title = "Review"
respond_to do |format|
format.html # show.html.erb
end
end
When I see this this code ..
Post.find_by_guuid(params[:id])
it would seem wrong but it works.
I don't understand why I can't write it out like this:
Post.find_by_guuid(params[:guuid])
Why do I still have to pass in the params[:id] when I'm not using it?
Looking for feedback on whether my approach is correct or anything else to consider.
Even though it works it doesn't always mean it's right.

Type rake routes in your console, and check the output of the routes. You'll see the fragment ':id' in some of them, that's where the params[:id] comes from. It's a rails convention : when you use resources in your routes, the parameter is named id. I don't know if you can change it (while keeping resources; otherwise you could just go with matching rules), but you shouldn't anyway : even if it seems not very logic, it actually has sense, once your understand how rails routing works.

Related

Rails nested resources and routing - how to break up controllers?

I have the following models:
Post
Tag
TaggedPost (from which Post and Tag derive their associations by has_many :through)
And I have the following routes.rb file:
resources :tags
resources :posts do
resources :tags
end
So when I navigate to, say, /posts/4/tags, that will shoot me into the index action for the Tag controller with the post_id value set in the parameters array. Cool.
My question is though, now that I'm accessing the nested tags resource under posts, should I be hitting the Tags controller still? Or should I setup some other controller to handle the nested nature of tags at this point? Otherwise I have to build additional logic into the Tags controller. This can be done of course, but is this the common way of handling nested routes and resources? The code I have in the index action for the Tags controller is as follows:
TagsController.rb
def index
if params[:post_id] && #post = Post.find_by_id(params[:post_id])
#tags = Post.find_by_id(params[:post_id]).tags
else
#tags = Tag.order(:name)
end
respond_to do |format|
format.html
format.json {render json: #tags.tokens(params[:q]) }
end
end
I can see the code in this controller growing increasingly large, as I plan for many additional resources to be associated with tag resources. Thoughts on how to break this out?
Summary of questions:
If a resource is nested, should the nested resource be going through a different controller representing the nested nature of the resource? This is opposed to going through the normal controller as I am in the code example that I provided.
If so, how should these controllers be named and setup?
Let me know if you need more information.
I think the best solution is to split up controllers:
resources :tags
resources :posts do
resources :tags, controller: 'post_tags'
end
And then you have 3 controllers. Optionally, you can inherit
PostTagsController from TagsController to do something like:
class PostTagsController < TagsController
def index
#tags = Post.find(params[:post_id]).tags
super
end
end
If the difference is only the retrieval of tags, you can:
class TagsController < ApplicationController
def tags
Tag.all
end
def tag
tags.find params[:id]
end
def index
#tags = tags
# ...
end
# ...
end
class PostTagsController < TagsController
def tags
Product.find(params[:product_id]).tags
end
end
Use that methods and simply override tags in the inheriting controllers ;)
All you are doing with nested resources is changing the routing URL. Only thing you would have to do is make sure you are passing the proper id (in your case post)to the tag controller. Most common error is the Can't Find *** ID.
If you don't nest a profile route into a user route it would look like this
domain.com/user/1
domain.com/profile/2
When you nest the routes it would be
domain.com/user/1/profile/2
That is all that it is doing and nothing else. You don't need additional controllers. Doing nested routing is just for looks. allowing your user to follow the association. The most important thing about nesting routes is that you make sure you make the link_to's to the right path.
When not nested: it would be
user_path
and
profile_path
when it is nested you would need to use
user_profile_path
rake routes is your friend to find out how the routes have changed.
Hope it helps.

Passing :new to Rails url_for

Maybe I'm stupid but Rails provides this nifty syntax for generating URL's like so:
url_for([user, comment]) # => /users/1/comment/1
Passing :edit allows me to create something like this:
url_for([:edit, user, comment]) # => /users/1/comment/1/edit
but is there some way to do following?
url_for([:new, user, comments]) # => NoMethodError: undefined method `new_user_comments_url'
UPDATE: Added more information.
My routes.rb:
resources :users do
resources :comments
end
resources :posts do
resources :comments
end
My problem here is, that I can't use Rails auto-generated url helper (user_comments_url), because I'm sharing the views for both user comments and post comments.
There are two workarounds (but no one feels like the "Rails"-way) for my problem:
Adding logic to the view, e.g. some if conditions.
Defining my own url helpers like new_parent_comment(user_or_blog).
Ok, found a solution, but I'm not sure if this is the intended one:
url_for([:new, user, :comment]) # => '/users/1/comments/new'
url_for([:new, post, :comment]) # => '/posts/1/comments/new'
Stuck with the same problem, and found next solution (tested on Rails 5.2):
url_for([user, Comment, action: :new])
where Comment model class name.
By the way, action also could be :edit.
According to the Rails Docs url_for uses the class name of the object passed to generate the RESTful route. It also states that with nested routes it can not make this assumption correctly:
If you have a nested route, such as admin_workshop_path you’ll have to call that explicitly (it’s impossible for url_for to guess that route).
I would suggest using a named route here something like new_user_comment_path(). I am assuming you have set up your routes.rb something like:
resources :users do
resources :comments do
end
end
Additionally you can run rake routes to print out the proper names for all your routes.
Hope this helps,
/Salernost
Could this simply be a typo? I think the last line should read comment, not comments:
url_for([:new, user, comment])
(Assuming your comment variable has been defined.)

Redirecting from a namespaced controller using a hash

I have a double namespace situation, where my controllers look like this:
CandidateController
Candidate::PerformanceController
Candidate::Performance::ReviewController
In Rails 2, I was able to use redirect_to from the Candidate::Performance::ReviewController controller in order to redirect to an action in the CandidateController, like so:
class Candidate::Performance::ReviewController < ApplicationController
before_filter :ensure_manager
# ...
def ensure_manager
if !current_user.manager?
flash[:warning] = t(:must_be_manager)
redirect_to :controller => '/candidate', :action => :index
end
end
end
The / in controller => '/candidate' would allow Rails to redirect from app.com/performance/reviews to app.com/candidate.
However, this seems to not work the same in Rails 3.1. Instead, my redirect_to goes to app.com/candidate//candidate. What is the correct way to specify a "absolute" controller within a redirect_to hash (ie. without using a path helper)?
Update: I know this would be infinitely easier if I just use named route helpers (ie. candidate_path). Unfortunately, there is a lot of legacy code in our codebase which doesn't use RESTful routing and instead uses the default "catch-all" route; ie. we have a lot of actions with no named route to fallback on.
I wonder if something else is wrong. In the doc:
In particular, a leading slash ensures no namespace is assumed. Thus,
while url_for :controller => ‘users‘ may resolve to
Admin::UsersController if the current controller lives under that
module, url_for :controller => ’/users‘ ensures you link to
::UsersController no matter what.
And I don't think it changed...
Also, shouldn't the catch-all routes be after the default routes in your config?
I think that redirect_to :controller => ... uses url_for to build the url, so in the end, if your custom routes catches /candidates, I don't really see the difference.
Some people have the same problem: https://github.com/rails/rails/issues/2575
Patching actionpack/lib/action_dispatch/routing/route_set.rb line 434
as follows fixes this: if !named_route && different_controller? &&
!controller.starts_with?('/')
If anyone else runs into this problem, it seems to be a known issue (unsure whether they consider it a bug or not, given the lack of response on the issue page from anyone working on Rails).
Until they patch it (assuming they do), I've added the following little monkey patch into an initializer, based on the code given in the original post of that issue:
module ActionDispatch
module Routing
class RouteSet
class Generator
def use_relative_controller_with_absolute_paths!
return if controller.starts_with?('/')
use_relative_controller_without_absolute_paths!
end
alias_method_chain :use_relative_controller!, :absolute_paths
end
end
end
end
Hope this can help someone else!
Update: It seems that this was fixed in Rails here.

Rails 3.1 custom controller action keeps asking for ID even when route is specified

I'm trying to add a custom action ('last_five') to a controller.
My routes are specified as:
people_last_five GET /people/last_five(.:format) {:action=>"last_five", :controller=>"people"}
(i.e. that's the output of rake_routes).
But when I browse to /people/last_five I get the following error.
Started GET "/people/last_five" for XXX.XX.XXX.XXX at Sun May 15 22:03:18 +0000 2011
Processing by PeopleController#last_five as HTML
User Load (1.4ms)^[[0m SELECT users.* FROM users WHERE users.id = 3 LIMIT 1
Completed in 86ms
ActiveRecord::RecordNotFound (Couldn't find Person without an ID):
I thought this was a problem in my routes.rb
In my routes.rb I currently have:
get 'people/last_five'
resources :people
I've also tried
resources :people do
get 'last_five', :on => collection
end
but that gives the same results.
Why is rails trying to get an ID when there is no "/:id/" in the route?
This even happens when I specify the route as '/people/:id/last_five' and pass it a dummy id. In that case it still tells me ActiveRecord::RecordNotFound (Couldn't find Person without an ID).
I have this problem even when I reduce the action itself to a stub for debugging, so I don't think that's the problem. In my controller:
# GET /people/last_five
def last_five
logger.info "LAST FIVE IS BEING CALLED"
##people = Person.last_five
#respond_with #people do |format|
# format.json { render :json => #people }
#end
end
Any idea what's going on here? It seems like rails is being told to get an ID by something outside of routes.rb. I've looked everywhere I can think.
Any leads are HIGHLY appreciated.
Thanks.
EDIT:
My PeopleController begins like so:
before_filter :authenticate_user!, :except => []
filter_resource_access
respond_to :html, :js, :json
Per the discussion on your questions, the cause is a before/around filter interfering rather than an issue with your specific action. Your application is searching for a User, so it may be authentication-related.
Are you sure this goes in Control, and not in Model? Rails doesn't want Model stuff in Control.

Rails 3 route scoping and/or nesting

I am having trouble scoping routes that I don't want to nest.
I have the following routes:
resources :foos
resources :bars
resources :bazs do
resources :hellos
resources :worlds
end
The foo, bar, and baz models all belong_to a user model. I don't want to nest another layer, but I do want to have a prefix in my url that corresponds to a user's permalink attribute (similar to each github repo prefixed by a username). So I have a before filter on all of my controllers
def get_scope
#user = User.find_by_permalink(params[:permalink])
end
Modified to_param thanks to #cowboycoded
class User < ActiveRecord::Base
def to_param
permalink
end
end
I wrapped those routes with
scope ":permalink", :as => :user do
#nested routes here
end
Now everything works fine as long as I pass #user to every non-index route. It doesn't seem very dry to have to go back to all of my views and replace (#foo) with (#user, #foo) when it is already scoped.
Unless I am mistaken, the to_param method simply replaces :id so that urls such as /users/:id appear as users/permalink instead of users/1. I attempted to use this :id in my scope, but it conflicts with foo's :id param and breaks everything. Maybe there is a connection to paths that I am missing?
Thanks for any suggestions that you may have!
Have you tried using the to_param method in your model? This will allow you to override the default and use something other than id, and will work with the URL helpers
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-to_param
Example from documentation:
class User < ActiveRecord::Base
def to_param # overridden
name
end
end
user = User.find_by_name('Phusion')
user_path(user) # => "/users/Phusion"
I'm not sure how well this plays with scope, since I haven't tried it, but I guess its worth a shot.