Help with Rails routes - ruby-on-rails

Say if I have a controller, profile, which has just two actions. The first one is list, which will just show the list of names. I want these names to be links which will then take you to a page that shows the full profile. So I need a second action, view, which can then bed fed a parameter to indicate which profile to view.
For example: I would access /profile/list, then if I want to view John's profile, I will click on his name which should take me to /profile/view/john. My view action will read the john parameter and then make the appropriate database queries.
What changes do I have to make to routes.rb for this to happen? Cheers.

I'd rather use the default :controller/:action/:id route to protect against cases where there are 2 John's in the list.
To have a custom route like you mentioned
edit Routes.rb to include a new one
map.connect ':controller/:action/:user_name'
Now a request like profile/view/john should reach you as
#params = {:controller => "profile", :action=> "view", :user_name => "john"}
Use the params[:user_name] value to locate and display the relevant record in the controller's view action. You can also want to setup some requirements on the :user_name part of the url, e.g. it has to match /SomeRegexpToValidateNames/
map.connect ':controller/:action/:user_name',
:requirements => {:user_name => /\w+/}

if you want to identify profiles by name, as "/profile/view/john" you can use the permalink_fu plugin
http://www.seoonrails.com/even-better-looking-urls-with-permalink_fu
which will keep you out of trouble when there's name duplication...

If the model is "user" you have to override the to_param method. This will allow you to return the "id-name" of the user instead of the "id". (ex: /profile/view/23-john)
I know, it's not exactly what you asked for but this should be a easy solution.
class User < ActiveRecord::Base
def to_param
"#{id}-#{name.gsub(/\W/, '-').downcase}"
end
end
And just add a simple resource declaration to the routing configuration:
map.resources :users

Related

How do I get the format of my URLs to be username/controller/:id in Rails 3.1?

I want it similar to the way Twitter handles the URLs for its tweets.
For instance, right now my URL looks like this: mydomain.com/feedbacks/1/, where feedbacks is the name of the controller.
I want it to look like: mydomain.com/username/feedbacks/1/ which is similar to Twitter's: twitter.com/username/status/:id/.
My routes.rb looks like this:
resources :users do
resources :feedbacks
end
When I have it like this, it gives me the URLs as mydomain.com/users/1/feedbacks, but I want the actual username in the URL.
How do I get that?
Thanks.
Edit 1: If you are adding another answer to this question, please make sure it addresses my comments/questions to the answer already given. Otherwise it will be redundant.
scope ":username" do
resources :feedbacks
end
From the docs:
This will provide you with URLs such as /bob/posts/1 and will allow
you to reference the username part of the path as params[:username] in
controllers, helpers and views.
UPDATE:
I have tested and confirmed the accuracy of paozac's answer. I'll clarify it a bit.
Suppose you had a #feedback object with an id of 12, and the associated user had a username of foouser. If you wanted to generate a URL to the edit page for that #feedback object, you could do the following:
edit_feedback_url(:username => #feedback.user.username, :id => #feedback)
The output would be "/foouser/feedbacks/12/edit".
# A URL to the show action could be generated like so:
feedback_url(:username => feedback.user.username, :id => feedback)
#=> "/foouser/feedbacks/12"
# New
new_feedback_url(:username => current_user.username)
#=> "/foouser/feedbacks/new"
Additionally, as noted by nathanvda in the comments, you can pass ordered arguments which will be matched with the corresponding dynamic segment. In this case, the username must be passed first, and the feedback id should be passed second, i.e.:
edit_feedback_url(#feedback.user.username, #feedback)
Also, if you need help handling the params from the controller, I suggest creating a new question specific to that.
Once you have defined the scope like dwhalen says you can generate the url like this:
feedbacks_url(:username => 'foo')
and get
http://www.example.com/foo/feedbacks
or
edit_feedback_url(:username => 'foo', :id => 1)
and get
http://www.example.com/foo/feedbacks/1/edit

Rails: Difference between List and Index

I appreciate this is an incredibly noob question, but having Googled multiple combinations of search terms I regretfully am still in the dark. These things can be difficult when one doesn't know and so obvious when one does.
I have semi-home page where incoming customers can choose to see a queried list or do a handful of other things. This isn't a home page but a sort of mini 'switchboard' within the site.
The seven standard RESTful Rails controller methods are (as I understand them):
List # shows a list of records generated with .find(:all)
Show # shows details on one record
New # initiates new record
Create # saves and renders/redirects
Edit # finds and opens existing record for edit
Update # updates attributes
Delete # deletes record
What to use when some users need to see a selected 'list' of records that isn't literally .find(:all)? How would this work given I still need a list function that gives me .find(:all) for other purposes?
I've heard of 'index' being used in Rails controllers, but I don't know the difference between index and list.
For best practice and best design, what controller methods would you use for a mini-switchboard (and other intermediate pages such as 'About Us')?
Any specific answers would be a bit more useful than links to http://guides.rubyonrails.org/action_controller_overview.html etc. :) Thanks very much.
First, I think it's important to note that the "standard methods" are neither standard nor methods in a sense. These are considered actions, and are only standard in that they are the conventions used with scaffolding. You can create any number of actions and group them logically with a controller.
If you open up [Project]/config/routes.rb and read through the comments, I think you'll understand a little better how controllers and actions map to a specific route. For instance, you can create a named route to the login controller's login action and call it authenticate by adding to the top of your routes.rb:
# ex: http://localhost/authenticate
map.authenticate 'authenticate', :controller => 'login', :action => 'login'
# map.[route] '[pattern]', :controller => '[controller]', :action => '[action]'
# ex: http://localhost/category/1
map.category 'category/:id', :controller => 'categories', :action => 'list'
# ex: http://localhost/product_group/electronics
map.search 'product_group/:search', :controller => 'products', :action => 'list'
To partially answer your question, you may want to consider adding a category model and associating all products to a category. then, you can add a named route to view items by category as in the code block above.
The major benefit to using named routes is that you can call them in your views as category_url or category_path. Most people don't want to do this and rely on the default route mappings (at the end of the routes.rb):
# ex: http://localhost/category/view/1
map.connect ':controller/:action/:id'
# ex: http://localhost/category/view/1.xml
# ex: http://localhost/category/view/1.json
map.connect ':controller/:action/:id.:format'
The key thing to mention here is that when a URI matches a route, the parameter that matches against a symbol (:id, or :search) is passed into the params hash. For instance, the search named route above would match a search term into params[:search], so if your products have a string column called 'type' that you plan to search against, your products controller might look like:
class Products < ApplicationController
def list
search_term = params[:search]
#products = Product.find(:all, :conditions => ["type = ?", search_term])
end
end
Then, the view [Project]/app/views/products/list.html.erb will have direct access to #products
If you'd really like an in-depth view into Ruby on Rails (one that is probably 10 times easier to follow than the guide in the link that you posted) you should check out
Agile Web Development with Rails: Second Edition, 2nd Edition

Rails routing of a controller's functions query

So I've got a Users controller, and it has (amongst others) a function called details.
The idea is that a user can go to
localhost:3000/user/:user_id/details
and be able to view the details of :user_id.
For example, I have a user called "tester".
When I go to the uri: http://localhost:3000/users/tester/details
I'd want the details function to be called up, to render the details view, and to display the information for the user tester.
But instead I get an error saying that
No action responded to tester. Actions: change_password, create, current_user, details, forgot_password, index, login_required, new, redirect_to_stored, show, and update_attributes
And I understand that to basically mean that if I wanted to access details, I should really be using
http://localhost:3000/users/details
Except that that isn't really working either... >.<
That is instead bringing me to http://localhost:3000/users/details/registries
(which is the default path that I'd stipulated for anybody trying to view users/:user_id, so again, that's working the way I wanted it to)
Point is: Can anybody help and tell me how I can go about getting
users/:user_id/details to work the way I want it to and display the details of :user_id?
Thanks!
Are you using resources? If your routes look like:
map.resources :users
You could make it:
map.resources :users, :member => { :details => :get }
That would allow GET requests for the URL /users/:id/details
More info here: http://guides.rubyonrails.com/routing.html#customizing-resources
I think that your problem is with setting routes in such way, that instead of :user_id you have :login (or whatever) in url /users/tester instead of /users/34. Probably you should take a look at to_param (1st example, 2nd example, 3rd example).
If you want to have another option in routes (besides default REST routes), you can add :member => {:details => :get} if you are using map.resources (#dylanfm answer) or just map it like in #Salil answer.
In order to get routes like "users/:user_id/details" change following in routes.rb
map.users 'users/:user_id/details', :controller => 'users', :action=>'details'

New Action Not Working

I have a standard User controller with the normal set of actions (index, show, new, edit, etc) and I'm trying to add a new action named 'profile'. I added the following code:
def profile
#user = User.find(session[:user_id])
end
I also created a new view for the action (app/views/users/profile.html.erb), but whenever I try to view that page I get an error:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with ID=profile
...
Apparently it's hitting the show action. I'm guessing that means I need to add something to my routes to make this work, but I don't know what. So far I just have the two default routes and the map.root line which I uncommented:
map.root :controller => "home"
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
So really I have two questions:
What do I have to do in order to enable my new action?
Why don't the existing routes cover this situation? Other urls consisting of just the controller and action work just fine (e.g. http://localhost:3000/users/new). Why not this one? Shouldn't it just evaluate to :controller = users, :action = profile, :id = nil?
Try putting something like this in your routes.rb file:
map.user_profile '/users/:id/profile', :controller => "users", :action => 'profile', :conditions => {:method => :get}
I think possibly the reason it's doing this is because you are not matching either of the defaults, because you are not setting :id (even though it is detecting your action as the id). I don't know what your URL looks like, but I have a feeling that if you tried http://localhost:3000/users/123124124/profile, it MIGHT work, even without the new line in routes.
Are you intentionally trying to get the id from session[:user_id] instead of params[:id]? Is this supposed to be displaying a public profile?
Are you sure, that the session contains the user_id the first time you load the page?
Hard to say without seeing all the code, but my guess is there may be some strangeness because your model and controller have the same name. I'd try renaming the controller before changing anything else (remember to change the name of the views/users directory too).
See also this other stack overflow post: Rails cannot find model with same name as Ruby class
Long shot maybe.
Solution for your problem configure your routes.rb in such a way that id should be passed as the parameter .
configure in routes.rb as below
map.profile '/profile/:id',:controller=>'users',:action=>'profile'
when you want to access your profile page use it with the following URL
http://localhost:3000/profile
Make sure once you login , u handle the session and store the userid in the session variable .
Good luck !

Validate no routing overlap when creating new resources in Ruby on Rails

I've got a RESTful setup for the routes in a Rails app using text permalinks as the ID for resources.
In addition, there are a few special named routes as well which overlap with the named resource e.g.:
# bunch of special URLs for one off views to be exposed, not RESTful
map.connect '/products/specials', :controller => 'products', :action => 'specials'
map.connect '/products/new-in-stock', :controller => 'products', :action => 'new_in_stock'
# the real resource where the products are exposed at
map.resources :products
The Product model is using permalink_fu to generate permalinks based on the name, and ProductsController does a lookup on the permalink field when accessing. That all works fine.
However when creating new Product records in the database, I want to validate that the generated permalink does not overlap with a special URL.
If a user tries to create a product named specials or new-in-stock or even a normal Rails RESTful resource method like new or edit, I want the controller to lookup the routing configuration, set errors on the model object, fail validation for the new record, and not save it.
I could hard code a list of known illegal permalink names, but it seems messy to do it that way. I'd prefer to hook into the routing to do it automatically.
(controller and model names changed to protect the innocent and make it easier to answer, the actual setup is more complicated than this example)
Well, this works, but I'm not sure how pretty it is. Main issue is mixing controller/routing logic into the model. Basically, you can add a custom validation on the model to check it. This is using undocumented routing methods, so I'm not sure how stable it'll be going forward. Anyone got better ideas?
class Product < ActiveRecord::Base
#... other logic and stuff here...
validate :generated_permalink_is_not_reserved
def generated_permalink_is_not_reserved
create_unique_permalink # permalink_fu method to set up permalink
#TODO feels really ugly having controller/routing logic in the model. Maybe extract this out and inject it somehow so the model doesn't depend on routing
unless ActionController::Routing::Routes.recognize_path("/products/#{permalink}", :method => :get) == {:controller => 'products', :id => permalink, :action => 'show'}
errors.add(:name, "is reserved")
end
end
end
You can use a route that would not otherwise exist. This way it won't make any difference if someone chooses a reserved word for a title or not.
map.product_view '/product_view/:permalink', :controller => 'products', :action => 'view'
And in your views:
product_view_path(:permalink => #product.permalink)
It's a better practice to manage URIs explicitly yourself for reasons like this, and to avoid accidentally exposing routes you don't want to.

Resources