I have an application, built with Rails 3.1.3, that has products and categories. The categories are related to other categories, so a category can be a parent or a child category. The products are then related to a child category.
Now, I'm thinking about how I should define the routes. Is it a good idea to somehow nest the categories and products resources? Ideally, I would like URLs like this:
example.com/parent/child/product-1234
like this:
example.com/clothes/underwear/some-socks-1234
or maybe like this to keep it restful?
example.com/p/clothes/c/underwear/....
But maybe that's a bit messy to achieve with the routes? I would have to nest the category with itself I guess?
Any ideas on how to achieve something like this?
EDIT:
Do I create the category routes like this:
resources :categories, :as => "parent" do
resources :categories, :as => "child"
end
or similar? It's not that important to have the product nested inside the categories. Maybe it will just make it hard to manage...
You can either be RESTful or not, you can't have it both ways.
If Category is a resource, then you can define routes with resource :categories statement and use Rails built-in support for REST. But then all categories should be accessible through categories/id. If you want different categories be accessible through different routes, then you are not RESTful and you can't use Rails REST support.
Alternatively you can say that parent and child categories are different resources and be RESTful again.
Edit: looking at my answer now (after it was accepted) I feel like I was probably too stiff. The answer should probably be "it depends" (as usual). If the application is CRUD (admin), I would stick to plain categories and nested products (shallow). On the storefront where you need nice looking url you can totally have non-REST routes and actions. It just mean more coding.
I would add an extra route besides the RESTful ones to support your URLs. This ignores the categories (they don't even have to exist) and routes directly to the product.
...
resources :products
resources :categories
match ':parent/:child/:product' => "products#show"
...
In your show action you can simply check which of params[:product] and params[:id] that is set and handle the different routes.
params[:parent] and params[:child] will also be available but i would suggest just looking for the product and get the category through that object since that should be more reliable.
Related
the title sounds strange, so is my problem... I am building a simple app - I have a restaurant model which has_many meals that users can add to their carts as orders(the order model has a meal's id and a quantity). Now, I would like to be able to add options to the meals - e.g. a pizza meal would have "small", "medium", and "large" option which would affect the price of this item. How do you think is the best way to implement this?
My problem is that each meal might have different options, and options can have different prices. Therefore, I cannot just add an attribute "option" as a string. On the other hand, if I use another model, there would be too much nesting in the routes which I read is a bad practice:
resources :restaurants do
resources :meals do
resources :options
end
end
How to avoid this? Thank you for any suggestions!!
There's no reason not to have an options model to hold each of your options as necessary - though you should really consider a shallow route for restaurants as the restaurant itself has nothing to do with options. So instead your routes might look like:
resources :restaurants, shallow: true do
resources :meals do
resources :options
end
end
If you look at your routes now (rake routes) you'll see how much they change.
But its worth considering that you don't need the resources for options. A visitor won't be creating new options. They'll be adding existing options to a collection (meal + option = order .. or something similar) -- so you might only have that route in the admin namespace for creating new options for a menu.
I am little bit confused in using rails route. I need some suggestion about customizing my url.
This is my current url
http://localhost:3000/posts/product/41?product_id=2
and
http://localhost:3000/posts/product/41?model_id=24&product_id=2
This is my link
<%= link_to product_model.name, controller: :posts,action: :product,product_id: params[:product_id],model_id: product_model.id
Logically product should come first in url. But why model prefers first here.
And i need my url something like this
http://localhost:3000/posts/product/41/mobile
and
http://localhost:3000/posts/product/41/mobile/nokia
Since i am not familiar with rails route i didn't write any special coding in my route
Here is the simple route exist
resources :posts
Ok your question here actually contains two different problems, so i will give suggestions to both.
1. Nested resources
Your first problem is to use "nested routes". Rails guide has a long and good article about routes and how to write and use them, including nested routes. You can check it out here: http://guides.rubyonrails.org/routing.html#nested-resources.
However in your situation would the solution look something like this:
resources :category do
resources :sub_category do
resources :products do
resources :models
end
end
end
You can now greate links like this
<%= link_to product_model.name, category_sub_category_product_model_path(#category, #sub_category, #product, product_model) %>
You can see that i have removed posts, see 3. Refactor design to see why. If you really want this as a action on posts, should you however do something like this (but would recommend this!):
get "posts/product/:category_id/:subcategory_id/:product_id/:model_id", to "posts#product", as: :posts_product
This would be used like this in your views:
<%= link_to product_model.name, posts_product_path(#category, #sub_category, #product, product_model) %>
2. Pretty URL's
Your second problem is to use model names instead of id's in your urls. The simpels solution for this is having a unique attribute on your model that you can use instead of id, and then just add a to_param method. Fx for product could we do something like this:
class Product < ActiveRecord::Base
def to_param
name
end
end
Ryan Bates have made a good screencast about this: http://railscasts.com/episodes/63-model-name-in-url-revised. If you want something more flexible should you use the gem Friendly Id. And again does Ryan comes to the rescue with another great RailsCast: http://railscasts.com/episodes/314-pretty-urls-with-friendlyid.
3. Change design
Ok well so this is just my opinion, feel free to ignore it. But their is some bad practices and signs in your examples, so let me just quickly go through what i think you should improve on.
Restful actions
You should, when possible always avoid creating controller actions that is not restful (simply put is the base actions index, show, new, create, edit, update and destroy the only restful actions). In your example does this mean that the product action of the posts controller should be changed to something restful. Why not move it to the product model controller and call it "show"?
Deeply nested resources
You should avoid nesting your routes to deeply. Is it really important to show both the category, the sub category, the product AND the model in your url? Maybe that's how your models are associated internally in your application but why should the user know this? If you don't have a list of subcategories at "/posts/product" and a list of products at "posts/product/41" is there no reason to have so long a route. A rule of thumb is "nest no deeper then two levels", ie. ":category/:sub_category". Further more does short routes mean better SEO.
As i said, feel free to ignore these suggestions, your application would work without these changes. However changing these things would greatly help you structure you code, and keep your codebase clean and maintainable. These rules and principles is not something i have just conjured out of nothing, but very accepted principles in the Rails community. You can google each of these principles or patterns and see a lot of articles and posts on why it's a good idea to follow them, especially when you work with Rails.
Resources
Rails Routing from the Outside In — Ruby on Rails Guides
norman/friendly_id - Github
#314 Pretty URLs with FriendlyId - RailsCasts
#63 Model Name in URL (revised) - RailsCasts
Take a look at the friendly-id gem
There's a great RailsCast about it
Add this to your model inmodel.rb
def to_param
name
end
and then add
#model = Model.find_by_name(params[:id]) to your show method, then you can get the url as you mentioned above.
PS: You Should have name field for Model table in your schema.
I think you are looking for nested routes. Please refer this link http://guides.rubyonrails.org/routing.html#nested-resources
and use to_param method in the model if you want to display model_name instead of id as explained by #Ajay Kumar
def to_param
name
end
where name is the model attribute for that specific model.
Why not a namespace?
namespace :posts do
resources :products
end
This should do I think..
Namespace does not include restful ids into the scope..
I'm still trying to get my head around actions and routes. I more or less understand how to user forms with the build-in controller actions like create, show, etc. What I want to do for a demo app is imitate a school's class schedule, where I have Courses and Students with a has_and_belongs_to_many relationship.
I'm using Mongoid, and I can add students to a course and vice versa using the console, but I can't figure out how to do it with a form. Would adding students to a course even be a controller action, or can I write and call a setter in the model somehow? If a controller action is better, what would the route look like?
If anyone knows of an example that does something similar, I'd love to examine it.
Thanks
It can be a controller action. If adding students to a course is a simple logic, you could add /courses/:course_id/Students/add. This means creating a courses folder, and a students_controller within it, with an add action.
Example (in your routes.rb)
resources :courses, :except => [:destroy] do
resources :students
end
More info: https://gist.github.com/jhjguxin/3074080
Is this what you are looking for?
I'm trying to understand the best way to architect a fairly simple relationship. I have a Job Model and a Category Model with a has_many relationship between them in a JobCategories model.
I'd like to have a page that lists all Jobs for a specific Category. Should the logic to pull this data be on the Category Controller (on the show action), or should I create a category method on the Job Controller? My gut tells me it should be on the Category side because a Category has Jobs, but it doesn't feel right that a Job would have the logic to pull all the Jobs for a given category.
Having said that, if I want the URL to be something that is more Job specific like:
domain/jobs/:id/{category-name} (for SEO purposes)
How would I structure the route so that it reads like the above, as opposed to
domain/categories/:id
which is what you'd get with resources :categories, only: [:show]
Thanks!
For a pretty slug, I'd suggest using FriendlyId on your categories model.
As for the routes, you will not get the desired route using resources :categories
One way to do it would be
resources :jobs, only: [] do
member do
get '/:slug' => 'categories#some_action'
end
end
the slug will be passed in your parameters.
This will yield a route like this
GET /jobs/:id/:slug(.:format) categories#some_action
UPDATE
the :slug is just an example for pretty url.
In your case you'd want to have :category_name. That would be passed into your controller through the params[:category_name].
One thing that I did start thinking when I re-read your question is that you want to show a list of jobs for a specific category. A url path like /jobs/:id/{category-name} shouldn't actually show a list of jobs as you are specifying an id which means a specific job. I think the url that you're looking to get is more along the lines of /jobs/{category-name}. Am I correct?
UPDATE 2
I suggest you read this Ruby On Rails Routing
UPDATE 3
Since you did want an url more like /jobs/{category-name}
You're routes should look like this
resources :jobs, only: [] do
collection do
get '/:category_name' => 'categories#some_action'
end
end
Good luck with your project! :D
Using Rails 3.1.1 and the gem acts_as_tree. I have googled the issue and checked similar questions here at SO (the answers are too old, or irrelevant).
I have a model called articles with a route that today looks like:
resources :articles, :path => '', :only => :show
resources :articles, :path => 'articles', :except => :show
I have three articles: "book", "chapter1" and "chapter2". Where book is parent to chapter1 and chapter2.
Today, my path to each article is: host.com/book, host.com/chapter1 and host.com/chapter2. I want the url path to be host.com/book/chapter1 and host.com/book/chapter2 , i.e. nested routes.
How can I create this in a clean simple manner?
Basically, I want a path that will be host.com/:parent_id/:parent_id/:id with N numbers of :parent_id. Pretty much how Wordpress-articles are routed.
I don't believe route globbers is the solution, but I might be wrong. It seems to give the same result for host.com/:id and host.com/foo/bar/:id which will result in duplicate content.
A)
If you have a solution for the routing and the only problem with it is that you're concerned about duplicate content issues, you could consider adding <link rel="canonical" href="..."> to the pages generated from those requests. It's not bulletproof though, as Google considers it a suggestion.
Not sure if the route globbers solution would take care of generating the URLs with parent IDs though.
B)
You don't need the parent IDs to perform the routing, correct? You just want to include them in the URLs and route those requests the same as if using the URLs like example.com/chapter1, correct?
If you'd consider a solution that's not purely at the Rails level, what about rewriting the URLs on those requests so that /:parent_id/:parent_id/:id becomes /:id before Rails processes it? That would be easier if there was a static prefix, like /articles/:parent_id/:parent_id/:id.
I imagine you'd need to write some helpers to generate the URLs with parent IDs for linking to those resources.
Duplicate Content
Either way, you'll need to generate URLs that include the parent IDs, so duplicate content issues probably aren't too likely if you only link to those resources using those URLs.
You have three "articles"... "book", "chapter1" and "chapter2" all represent same 'resources', named 'articles'. Same 'resource' cannot be nested. If you need nested routes you have to define separately parent resource and child resource. following code spinet may help you
class Book < ActiveRecord::Base
has_many :chapter
accepts_nested_attributes_for :chapters
end
class Chapter < ActiveRecord::Base
belongs_to :book
acts_as_tree :parent_id
end
match '*p1/*p2/*p3/.../*pn' => 'articles#show'
The ... is not literal, just define as many parameters as you need upto n.
URL:
host.com/book/chapter1
params[:p1] = 'book'
params[:p2] = 'chapter1'
params[:p3] = nil
URL:
host.com/book/chapter1/section2/sentence4
params[:p1] = 'book'
params[:p2] = 'chapter1'
params[:p3] = 'section2'
params[:p4] = 'sentence4'
params[:p5] = nil
That'd have to be your LAST route.
I think it would also make any catchall routes inoperable, but they're now commented out in the default routes.rb in Rails 3, If you use them, you'd have to manually specify all routes normally handled by the old style catchall routes.
And, if you have a controller named articles, you could never have a book titled 'articles' same with all your controllers, To be safe you probably have to rename all your controllers, i.e articles becomes X_articles. You could never have a book call X_articles then, and so on....
Totally untested.
What you're looking to do is use Rails for something it isn't made for. No matter what answer you get here, it either won't be RESTful, DRY, or make sense to be used with Active Record. Consider restructuring your idea, or bring your application to another platform if it's not too late.
Source to back up my claim: https://stackoverflow.com/a/174287/628859