User-friendly nested routes in rails - ruby-on-rails

I am a ruby/rails newbie and have a belongs_to relationship between, let's say, group and user (user belongs_to group).
I would like to have the following type of url:
www.mysite.com/abcd/user/1 - (NOTE "group" is not part of the URL)
where abcd is the group name and 1 is the user ID.
Is this easy to do? How do I go about it?
Thanks!

I'm not sure, but try this:
scope :path => '/:group_name' do
resources :users
end
You must find your group by params[:group_name] if you use this approach.

Yes, it is very easy to do in both Rails 2 and 3.
Assuming you're using Rails 3, you would do it like this
match ":group/user/:id", :to => "group#user"
When you use the match method, URL parts with a preceding colon will become parameters, so in your controller you would have params[:group] and params[:id] available. The :to paramater defines the controller and the action, so in this example it would send the request to the GroupController and the user action. :to can actually point to any Rack application end point, the "cont#action" is just a shorthand.
If you are using resources, you can set those up as normal, and then just define this non-standard route somewhere else in the stack and both will work side-by-side.

Related

How to organize similar routes for multiple Rails users?

Let's say I have an Accountant < User model, and a Worker < User model. They both need to have pages like 'Settings', 'Dashboard', etc.
Right now the paths are assigned and explicitly defined in routes.rb:
resources :accountants
get '/accountant/dashboard' => 'accountant#dashboard'
get '/accountant/dashboard/:date' => 'accountant#dashboard'
get '/accountant/settings' => 'accountant#settings'
resources :workers
get '/worker/dashboard' => 'worker#dashboard'
get '/worker/dashboard/:date' => 'worker#dashboard'
get '/worker/settings' => 'worker#settings'
Saving the "home" dashboard path in a session / as application level helper methods which rely on current user class both don't seem very Ruby-esque. Is there an alternative to this in Rails 4?
The better way for this situation is NameSpace, Rails provide us something called namespace and you can use it in the routes for generate different routes for different views in your case maybe works something like that:
namespace :accountants do
get 'dashboard'
get 'dashboard/:date'
get 'settings'
end
namespace :workers do
get 'dashboard'
get 'dashboard/:date'
get 'settings'
end
and that will generate a routes like that:
localhost:3000/accountants/1/dashboard
localhost:3000/workers/1/settings
it's just and example you always can read the official documentation about it, but it's a good way for organize your different routes thinking in the scalability.
Another option is using roles to manage the different users you have because of you extend of user model is not scalable with the time and it's going to be a little confuse in the future read this code
Regards !

Find path of a nested or non-nested resource

Working in Rails 3.2, I a polymorphic Subscription model whose subscribable_type may or may not be a nested resource. I'm trying to display the full URL link in an email view, but have no knowledge whether or not that resource is nested.
When I try url_for #model on a nested resource, it fails, expecting url_for [#parent, #model]. Unfortunately, I do not know how to discover the parent as defined in the Routes table.
Is there a way to identify the route path for a nested resource? If I could match the model to a route, I could fill in the necessary IDs.
As of right now, I've defined a method in my models called parent_resource :model that can be traversed, but I'm hoping there's a better way.
Within my routes.draw:
resources :projects do
resources :topics do
resources :comments
end
end
resources :subscriptions
(I realize I shouldn't be nesting so deeply)
Edit: Additional Information
My Subscription model is a resource I use to manage notifications. Subscribable types are provided a link that toggles the subscription for that user on that subscribable_type / subscribable_id on or off.
I then go through a Notifier < ActionMailer::Base which is provided the Subscription instance, and mail the user.
Through that setup, I'm trying to get the full url of subscription.subscribable which may be a Topic or a Project.
I realize that I could hammer out the conditions in this small case through a helper method, but I am curious to know how one would approach this if there were dozens of nested model pairs.
You mention subscription but your routes are completely different. I'm guessing the routes you gave were just an example then. I would start with trying to get rid of the custom parent_resource method you created. You can probably do the same thing simpler with adding a belongs_to through and maybe with conditions if you need too:
belongs_to :projects, :through => :topics, :conditions => ['whatever your conditions are']
I'd have one of those per parent type so I can do things like:
object.project.present?
And from there I could easily know if its nested or not and simplify things by letting rails do the parent traversal. That ought to simplify things enough to where you can at least figure out what type of subscription you have pretty easily. Next, I'd probably add some matched routes or try to cram an :as => 'somename' into my routes so I can call them directly after determining the nested part. One option would be something like this:
match "projects/subscription/:id" => "projects#subscription", :as => :project_subscription
match "other/subscription/:id" => "other#subscription", :as => :other_subscription
And so its pretty obvious to see how you can just specify which url you want now with something like:
if #object.project.present?
project_subscription_path(#object)
else
other_subscription_path(#object)
end
This may not be the best way to accomplish what I'm doing, but this works for me right now.
This builds a nested resource array off the shortest valid route helper and generates a URL:
(Tested in rails console)
resource = Comment.first
resource_name = resource.class.to_s.downcase
helper = Rails.application.routes.named_routes.helpers.grep(/.*#{resource_name}_path$/).first.to_s.split('_')
built = helper.slice!(-2,2) # Shortest possible valid helper, "comment_path"
while !(app.respond_to?(built.join("_").to_sym))
built.unshift helper.pop
end
built.pop # Get rid of "path"
resources = built.reverse.reduce([]) { |memo, name|
if name == resource_name
memo << resource
else
memo << memo.last.send(name.to_sym) # comment.topic, or topic.project (depends on belongs_to)
end
}
resources.reverse!
app.polymorphic_url(resources) # "http://www.example.com/projects/1/topics/1/comments/1"

Simple Acts_as_tree with nested_resources

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

Ruby on Rails path awareness

Lets consider the following situation.
There is products_controller which can be accessed from "Admin" and "Configure" sections of the Ruby on Rails application.
In the view I need to differentiate which section I am currently in (i.e. "Admin" or "Configure"). What would be there best practice of achieving the right result?
Couple of solutions come to mind?
Append the "referrer" option as a parameter and use it to distinguish where I came from (i think this would be super-ugly and break the nature of rest).
Create separate action pairs in the controller(i.e. new/create and admin_new/ admin_create).
What would be the right approach in the given situation?
If it is just for logging purposes, adding a parameter should be enough.
If logic of how things are handled depends on where user came from, go for different routes mapping to different actions.
If you don't wan't to add a parameter, but it is for logging purposes, you can also create non-conventional route:
resources :products, :except => [:new, :create] do
collection do
get products/new(/:section) => "products#new"
post products(/:section) => "products#craete"
end
end
Now you can have new_message_path(:section => "admin") and it will result in path /products/new/admin, you will have the :section available in params[:section].

How to map a new CRUD action in 'routes.rb'?

In my 'routes.rb' file I have this code:
resources :users
that maps my user's controller like this.
If I want to map the "reset" view/url for users (Path: /users/reset) what code I have to insert in the 'routes.rb' file?
Two options - I'm assuming you're just going to act on the session user so you don't need to pass in an id to operate on? If so, you'll need to make a few additional changes...
Use an explicit route:
match "/users/reset" => 'users#reset', :as => 'reset_user'
The 'as' part is optional.
Add a new route that operations on a 'collection'. This gets you your route but feels like a hack, I wouldn't recommend it.
resources :users do
collection do
get 'reset'
end
end
Do this:
resources :user do
member do
get 'reset'
end
end
See this section in the Rails Guide you referred to.

Resources