I have an store application, where I need to make custom routing system where URL stores categories for products. For example, http://example.com/languages/ruby/rails will display category#show named 'rails', that has parent named 'ruby', that has parent named 'languages' and and URL of http://example.com/languages/ruby/rails/store will display product in this category.
Currently I have:
category.rb
belongs_to :parent, class_name: 'Category'
has_many :categories, foreign_key: :parent_id
has_many :products
routes.rb
resources :categories, :path => '', :only => [:index, :show] do
resources :products, :path => '', :only => [:show]
end
root :to => 'products#index'
but it still stacks up to 2, e.g. URL http://example.com and http://example.com/languages shows list of categories/subcategories, but http://example.com/languages/ruby have params: {"action"=>"show", "controller"=>"products", "category_id"=>"language", "id"=>"ruby"}
Removing products from routes does not help at all - then it just says that No route matches [GET] "/language/ruby", although I assume It might cause need for extra check if current URL point on category or product later on.
Also I tried get '*categories/:id', to: 'category#show' variations
+ I am using friendly_id gem so that path do not look like http://example.com/2/54/111/6
I just want to find out what is the best ruby on rails solution for this kind of situations, when you need search engine optimizations + endless (e.g. no way to define how deep such recursion can go) nested resources that nest themselves (including fact that category/language/category/ruby/category/rails just looks ugly).
Note: most information I used is taken from Stack Overflow and railscasts.com (including pro/revised episodes), so mentioning a good source with information like this will be great too.
I solved this myself recently with a CMS I built on Rails recently. I basically construct the routes dynamically at runtime from the database records. I wrote this blog post on the strategy:
http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4
The core of the solution (adapting the blog post above) is simply iterate over the database records and construct the routes needed for each category. This is the main class for doing that:
class DynamicRouter
def self.load
Website::Application.routes.draw do
Category.all.each do |cat|
get cat.route,
to: "categories#show",
defaults: { id: cat.id },
as: "#{cat.routeable_name}_#{cat.name}"
end
end
end
def self.reload
Website::Application.routes_reloader.reload!
end
end
For the above, the Category model should implement a "routeable_name" method which simply gives an underscored version of the category name that uniquely names that category's route (its not strictly necessary, but helps when doing "rake routes" to see what you have). and the #route method constructs the full route to the category. Notice the defaults which sets the ID param for the category. This makes the controller action a very simple lookup on the category's ID field like so:
class CategoryController < ApplicationController
def show
#category = Category.find(params[:id])
end
end
Related
First off I am sure I am not asking this question correctly as this is an advanced Rails topic over my head right now. Here is what I have for routes:
concern :assetable do
resources :assets, concerns: [:workable, :trackable, :flagged, :historical, :commentable, :uploadable], shallow: true do
resources :assets, path: :components, as: :components, shallow: true#, only: [:index, :create, :new]
end
end
and my asset model:
has_many :assets, :as => :assetable, :dependent => :destroy
alias_attribute :components, :assets
My premise here is I have an Asset model that can polymorphicly belong to pretty much any other model in my Rails app - including another Asset. My my case I am limiting this case to one level deep and calling these Components. This is like having a car as an asset and it has an engine, transmission etc. as a component. For my purposes it's simple enough to make a common set of attributes that are shared between both the assets and components,
Now I could just create an entire new model but the entire data structure, views controllers etc are for the most part the same so I figured that was just a waste of time and makes it a mess to keep this code in sync.
As I go along now I am having to add some logic to my views to ensure the headings i.e. "Assets" vs. "Components" and I have a simple instance variable in my controller #asset_class which returns 'asset' or 'component'. The ugly part now is when I start working with routes. All the awesome Rails url helpers etc. fail me here because if I call url_for( ) on a component record I get /assets/etc/etc when I really want /components/etc/etc/. I essentially want to Assets nested under Assets to be represented as Component class not Asset.
Can I do this? I think this is easier way but I am open to other suggestions (and question edits) because I may be way off here on my logic and design.
** UPDATE **
Here is some more background from the first comment:
So in my Asset show controller I deliberately added an exception to illustrate this. Here is my url:
http://localhost:3000/components/5
and from my error console I have:
>> #asset
=> #<Asset id: 5, assetable_id: 1, assetable_type: "Asset", name: "...>
>> url_for(#asset)
=> "http://localhost:3000/assets/5"
>> component_path(#asset)
=> "/components/5"
>> asset_path(#asset)
=> "/assets/5"
I know that I could use component_path(#asset) but url_for is much cleaner if I can let Rails do all the work vs constantly having to add some conditional logic every time I want to add a link_to etc. I also see that I need to add some conditionals to my routes based on assessable_type as Assets should not show up in Components and vice-versa.
You might consider creating a Component class that inherits from Asset so that you can store all those in the assets table but treat them differently. This is generally called single table inheritance.
http://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html
You could define your components routes like so:
resources :components, controller: 'assets', path: :components, as: :components, shallow: true#, only: [:index, :create, :new], defaults: { components: true }
This uses the technique described in Specifying a Controller to Use section of the Rails Routing from the Outside In. It also uses the defaults option from the Defining Defaults section of the same guide to inform the controller that it's using a components route and not an assets route.
I currently have some routes that look like the following:
resources :contests do
member do
get :enter
end
end
This allows me to do an HTTP GET on a URL that looks like /contests/5/enter. Now, a user can go in, fill in some forms, and be able to submit an entry to the contest. So I'd also like to be able to POST to this URL. I tried doing the following:
resources :contests do
member do
get :enter
post :enter
end
end
This posts to the same controller#action as the GET member that I have specified, so it's not really intuitive. I'd like to be able to direct it to a separate action if at all possible. What's the best way of doing that? I am using Ruby on Rails 4 beta currently.
** UPDATE **
I tried the following but I get an ArgumentError exception when I start the server:
resources :contests do
member do
get :enter
post :enter => "contests#create_entry"
end
end
You can do something like this:
resources :contests do
member do
get :enter
post '/enter', to: "contests#create_entry", as: "create_entry"
end
end
However i agree with Ola Tuvesson, you should definitely create a new controller and routes for entries, even though you may not have a model, similiar to how you often have a session controller for login and logout. Something like this:
resources :contests do
resources :entries, only: [:new, :create]
end
You can specify the controller/action you want to point a route at.
get :enter => "controller#get_enter"
post :enter => "controller#post_enter"
I would suggest you make the entries for a contest a child model of contests. That would give you all the CRUD methods on entries and your routes would simply be:
resources :contests do
resources :entries
end
A GET of /contests/5/entries/new will give you the form for adding an entry and when this POSTs to /contests/5/entries it would create a new entry. It also makes it easy to list all entries for a competition etc. You can easily create the controller, model and the associated views with the scaffold generator, for example:
rails g scaffold Entry contest:references name:string email:string
The references column type tells the generator to link Contests to Entries in a one to many relationship. Job done.
Edit:
If you still want to rename your routes, here's how:
http://guides.rubyonrails.org/routing.html#overriding-the-named-helpers
HTH
i have a namespace "shop". In that namespace i have a resource "news".
namespace :shop do
resources :news
end
What i now need, is that my "news" route can get a new parameter:
/shop/nike (landing page -> goes to "news#index", :identifier => "nike")
/shop/adidas (landing page -> goes to "news#index", :identifier => "adidas")
/shop/nike/news
/shop/adidas/news
So that i can get the shop and filter my news.
I need a route like:
/shop/:identfier/:controller/:action/:id
I tested many variations but i cant get it running.
Anyone can get me a hint? Thanks.
You can use scope.
scope "/shops/:identifier", :as => "shop" do
resources :news
end
You will get those routes below:
$ rake routes
shop_news_index GET /shops/:identifier/news(.:format) news#index
POST /shops/:identifier/news(.:format) news#create
new_shop_news GET /shops/:identifier/news/new(.:format) news#new
edit_shop_news GET /shops/:identifier/news/:id/edit(.:format) news#edit
shop_news GET /shops/:identifier/news/:id(.:format) news#show
PUT /shops/:identifier/news/:id(.:format) news#update
DELETE /shops/:identifier/news/:id(.:format) news#destroy
http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
If you have those nike, adidas etc. in the database then the most straightforward option is to use match.
namespace :shop
match "/:shop_name" => "news#index"
match "/:shop_name/news" => "news#news"
end
However it seems to me that shop should be a resource for you. Just create a ShopsController (you don't need a matching model for it, just a controller). Then you can do
resources :shops, :path => "/shop"
resources :news
end
Now you can access the news index page (/shop/adidas) like this:
shop_path("adidas")
In the NewsController use :shop_id to access the name of the shop (yes even though it's _id it can be a string). Depending on your setup you may want news to be a singular resource, or the news method to be a collection method.
Also are you sure just renaming the news resource isn't something you want?
resources :news, :path => "/shop" do
get "news"
end
Keep in mind also that controller names and the number of controllers need not match your models. For example you can have a News model without a NewsController and a ShopsController without a Shop model. You might even consider adding a Shop model to your database if that makes sense.
In case this is not your setup then you might have oversimplified your example and you should provide a more full description of your setup.
I am using Ruby on Rails 3.2.2 and I would like to generate a proper url_for URL for a nested resource. That is, I have:
# config/routes.rb
resources :articles do
resources :user_associations
end
# app/models/article.rb
class Article < ActiveRecord::Base
...
end
# app/models/articles/user_association.rb
class Articles::UserAssociation < ActiveRecord::Base
...
end
Note: generated named routes are like article_user_associations, article_user_association, edit_article_user_association, ...
When in my view I use:
url_for([#article, #article_association])
Then I get the following error:
NoMethodError
undefined method `article_articles_user_association_path' for #<#<Class:0x000...>
However, if I state routers this way
# config/routes.rb
resources :articles do
resources :user_associations, :as => :articles_user_associations
end
the url_for method works as expected and it generates, for instance, the URL /articles/1/user_associations/1.
Note: in this case, generated named routes are like article_articles_user_associations, article_articles_user_association, edit_article_articles_user_association, ...
However, I think it isn't "good" the way routers are builded / named in the latter / working case. So, is it possible in some way to make the url_for method to work by generating named routes like article_user_association (and not like article_articles_user_association)?
I read the Official Documentation related to the ActionDispatch::Routing::UrlFor method (in particular the "URL generation for named routes" section), but I cannot find out a solution. Maybe there is a way to "say" to Rails to use a specific named router as-like it makes when you want to change the primary key column of a table with the self.primary_key statement...
# app/models/articles/user_association.rb
class Articles::UserAssociation < ActiveRecord::Base
# self.primary_key = 'a_column_name'
self.named_router = 'user_association'
...
end
Your UserAssociation model is in the Articles namespace, which gets included in the named route:
# app/models/articles/user_association.rb
class Articles::UserAssociation < ActiveRecord::Base
...
end
# route => articles_user_association
# nested route => article_articles_user_association
If you remove the namespace, you will get the route helper you are looking for:
# app/models/articles/user_association.rb
class UserAssociation < ActiveRecord::Base
...
end
# route => user_association
# nested route => article_user_association
Unless you have a really good reason for keeping UserAssociation in a namespace, don't.
I want to create a CMS like site where the user starts off with a some generic pages, i.e.
homepage
about
contact
etc
and from there can add child pages dynamically, for example
homepage
articles
article1
something
something-else
article2
about
contact
etc
To achieve this I'm planning on using some kind of self-referential association like
class Page < ActiveRecord::Base
belongs_to :parent, :class_name => 'Page'
has_many :children, :class_name => 'Page'
end
The one thing I'm struggling with is the route generation. Because pages can be added on the fly I need to dynamically generate routes for these pages and there is no way of knowing how many levels deep a page may be nested
So if I start off with the homepage:
/
and then start adding pages i.e.
/articles/article1/something/something-else/another-thing
How can something like that be achieved with the rails routing model?
Once you have some way to generate the URL string for your Page records (and I'll leave that part up to you), you can just map every page in config/routes.rb:
Page.all.each do |page|
map.connect page.url, :controller => 'pages', :action => 'show', :id => page
end
And have an observer hook the page model to reload routes when something changes:
class PageObserver < ActiveRecord::Observer
def reload_routes(page)
ActionController::Routing::Routes.reload!
end
alias_method :after_save, :reload_routes
alias_method :after_destroy, :reload_routes
end
Don't forget to edit config/environment.rb to load the observer:
# Activate observers that should always be running
config.active_record.observers = :page_observer
One solution to this prob is to dynamically load routes from hooks on your models. From example, a snippet from the Slug model on my site:
class Slug < ActiveRecord::Base
belongs_to :navigable
validates_presence_of :name, :navigable_id
validates_uniqueness_of :name
after_save :update_route
def add_route
new_route = ActionController::Routing::Routes.builder.build(name, route_options)
ActionController::Routing::Routes.routes.insert(0, new_route)
end
def remove_route
ActionController::Routing::Routes.routes.reject! { |r| r.instance_variable_get(:#requirements)[:slug_id] == id }
end
def update_route
remove_route
add_route
end
def route_options
#route_options ||= { :controller => navigable.controller,
:action => navigable.action,
:navigable_id => navigable_id,
:slug_id => id }
end
end
This inserts the route at top priority (0 in the routing array in memory) after it has been saved.
Also, it sounds like you should be using a tree management plugin and like awesome nested set or better nested set to manage the tree for your site.
You have to parse the route yourself
map.connect '*url', :controller => 'pages', :action => 'show'
Now you should have a params[:url] available in your action that is the request path as an array separated by the slashes. Once you have those strings its a simple matter to find the models you need from there.
That was from memory, and it's been a long while. Hope it works for you.
Look at RadiantCMS sources, they implement that functionality as far as i understand their self description.
I've implemented a similar functionality into a Rails gem, using self referential associations and a tree like js interface for reordering and nesting the "pages".
Templating language and authentication/authorization are left for the developer to implement.
https://github.com/maca/tiny_cms