A RoR v3 routing question? - ruby-on-rails

I was wondering if it is possible to route to something like this /:user_id user_id is a custom id that doesn't just use integers it uses other characters like so NM-001. Then in my controller I have #user = User.find(params[:user_id]). Then in view <%= #user.name %>

Yes, you can have such a route. However, if your :user_id will contain periods then you'll want to include
:constraints => { :user_id => /.*/ }
in the route options to keep Rails from trying to interpret the .whatever part of the :user_id as a format specifier.
Then, you'll get params[:user_id] in your controller and you can turn that into an object however you want. You'd probably want to do what mischa said in the comments:
#user = User.find_by_user_id(params[:user_id])
Also, if you really want to use /:user_id as your route, you'll want to make sure that none of your userids match any of your present or future top-level routes.

Related

url_for knowing only model symbol and an id

Knowing these things
model_sym = :users
user_id = 1
I can do this:
url = "#{model_sym.to_s}/#{user_id}"
But is there a way I could do something like this?:
url = url_for(model_sym, user_id)
I could first "find" the user to pass into url_for, but I'd rather not.
I think you could use polymorphic_url:
polymorphic_url([model_sym, user_id])
Resources
Having written that, it seems you're getting confused about the resourceful nature of Rails.
Built on Ruby, Rails is object-orientated, which means that everything you do needs to be tied to an object (model):
The reason why this is important is because all of Rails' helpers etc are built around objects. That's why when you create a new set of routes, you can simply call resources (as resourceful is to give the object a set of attributes / methods you can call)
--
Implementation
The problem you have is you're not basing your routes around any objects - you're simply
calling symbols / numbers. Although this will work, it's not the "right" way to create Rails functionality
Your ideal situation is to build objects, and pass them to the routing structure of your application. To build an object, you'd do the following:
#user = User.find params[:id] #-> builds object
<%= link_to "Users Path", #user %> #-> pulls route from object
Something like this:
url_for(:controller => model_sym.to_s, :action => :show, :id => user_id)
You can do send("#{ model_sym.to_s.singularize }_path", id) to get the URLs you want.
This would call user_path(1) in your example.

Rails routing URL folders for product categories on show actions

I'm trying to formulate some better urls for a "product" model I have, only on the show action. I'm currently using friend_id to generate pretty slugs, which is fine, but I'm trying to improve the URL flow if I can.
Current my paths work like this
example.com/products/pretty-url-slug
When saving a parictular product (to the Product Model), I also save a type_of attribute. Which could be android, iphone, windows
So I am trying to ultimately have robust URLS like this
example.com/products/iphone/pretty-url-slug
The problem is, that I don't have or believe I want an actual "iphone", "android", etc controller. But I'd rather just update a combination of the routes and show action to handle this properly.
So far I've attempted to solve this by using a catch all on the routes, but is not working correctly. Any suggestions or different ways to handle this elegantly?
routes
resources :products
# at the bottom of my routes a catch all
match '*products' => 'products#show'
# match routes for later time to do something with to act like a
# normal category page.
match 'products/iphone' => 'products#iphone_index'
match 'products/android' => 'products#android_index'
match 'products/windows' => 'products#windows_index'
show action in the products controller
def show
# try to locate the product
if params[:product].present?
slug_to_lookup = params[:product].split("/").last
type_of = params[:product].split("/").second
#product = Product.find_by_slug(slug_to_lookup)
else
#product = Product.find_by_slug(params[:id])
end
# redirect if url is not the slug value
if #product.blank?
redirect_to dashboard_path
elsif request.path != product_path(#product)
redirect_to product_path(#product)
end
end
This way to handle the problem sort of works, but I can't fiqure out how to append the type_of attribute and generate a valid URL.
What about defining your routes like this:
get ':controller/:action/:id/:user_id'
Here, Anything other than :controller or :action will be available to the action as part of params.
Thanks for the suggestion. I was actually able to solve this and pretty simple when I thought it over. This might be helpful for others.
In my routes I just created a route for every type of category I have. so every time a new category, I would need to add an additional route, example:
# match for each product category
match 'shop/iphone/:slug' => 'products#show', :as => :product_iphone
match 'shop/android/:slug' => 'products#show', :as => :product_android
match 'shop/windows/:slug' => 'products#show', :as => :product_windows
Then in the show action for products instead of directing, you would just render the products/show if the slug matches an existing product
#product = Product.find_by_slug(params[:slug])
Then in your views, you could link to a particular category like this
link_to "product", product_android_path(#product)

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 Routing with Query String

I have a problem where I need values passed in from a GET request and I don't know how to set up the routing definition.
My Category object has a type(string),a color(string) and many products. I want to create a simple web service that lets the caller get all of a Category's products by passing in the Category's type and color:
http://www.myapp.com/getProducts?catType=toy&color=red
or ?
http://www.myapp.com/categories/getProducts?catType=toy&color=red
How do I define the correct routing for this situation? Are there better ways to do this in a Restful manner... because I know that Rails is Restful, so if there is a way to do it "correctly" then that would be even better.
Thanks
Your first example:
map.getproduct '/getProduct', :controller => 'your_controller', :action => 'your_action'
In controller you will have catType and color in params hash:
params[:catType]
=> 'toy'
params[:color]
=> 'red'
Is there better way? Probably yes, but it depends on your needs. If you will always have catType and color parameters, than you can add route like this:
map.getproduct '/getProduct/:catType/:color', :controller => 'your_controller', :action => 'your_action'
You will have access to those parameters with params hash like in previous example. And your urls will look like this:
www.myapp.com/getProduct/toy/red
If your parameters may change, you can use route globbing:
map.getproduct '/getProduct/*query', :controller => 'your_controller', :action => 'your_action'
Then it will catch all request that has www.my.app.com/getProduct/... at the begining. But you will have more work in controller. You will have access to query with this:
params[:query]
and for www.myapp.com/getProduct/color/red/catType/toy it will give:
params[:query]
=> ['color', 'red', 'catType', 'toy]
So you have to parse it manualy.
One RESTful way to to do this would involve a product resource nested beneath a category resource, like so:
http://www.myapp.com/categories/toy/products?color=red
Your routes.rb would need to contain:
map.resources :categories do |category|
category.resources :products
end
Since my url above using the Category's type attribute for routing, I'm implying that each type is unique, like an id. It'll mean that whenever you're loading a category in the Categories controller (or anywhere else) you'll need to load the category with Category.find_by_type(params[:id]) instead of Category.find(params[:id]). I like routing categories this way whenever possible.
Your ProductsController controller index action would find products using lines like:
#category = Category.find_by_type(params[:category_id])
#products = #category.products.find(:all, :conditions => { :color => params[:color]} )
Remember, your Category model must contain the line:
has_many :products
It's probable a good idea to enforce that in the model with validations:
validates_presence_of :type
validates_uniqueness_of :type
To make routing work you should also overwrite the to_param method in the Category model to return type instead of id:
def to_param
self.type
end

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