Strange Rails Routes Behavior - ruby-on-rails

Following the Rails Guide on routing for nested resources, I have two models and my routes like so:
# media.rb
class Media < ActiveRecord::Base
has_many :captions, class_name: "Captions", dependent: :destroy
end
# captions.rb
class Captions < ActiveRecord::Base
belongs_to :media
end
# routes.rb
resources :medias do
resources :captions
end
When I run rake routes | grep captions I get the following, which seems incorrect. Some of my actions aren't nested the way I expect them to:
media_captions GET /medias/:media_id/captions(.:format) captions#index
POST /medias/:media_id/captions(.:format) captions#create
new_media_caption GET /medias/:media_id/captions/new(.:format) captions#new
edit_captions GET /captions/:id/edit(.:format) captions#edit
captions GET /captions/:id(.:format) captions#show
PUT /captions/:id(.:format) captions#update
DELETE /captions/:id(.:format) captions#destroy
As you can see, the index and create actions are properly nested but the other actions are not. Can anyone explain why this is happening?

Is it because your "Captions" class is plural? You're in for some pain if you're not following conventions. Your caption class file name should be caption.rb and look like so:
class Caption < ActiveRecord::Base
belongs_to :media
end

I believe it stems from the fact that the plural of "media" is "media". You can check that in the rails console:
2.0.0-p0 :001 > include ActionView::Helpers::TextHelper
2.0.0-p0 :002 > pluralize(2, "media")
=> "2 media"
So Rails thinks it's a singular resource.

I think routes are constructed this way to make it explicit that the caption is being edited, regardless as to which medias it belongs to.
Consider a has_and_belongs_to_many relationship, where a caption could belong to multiple medias. If you had the following routes:
/medias/1/captions/10/edit
/medias/5/captions/10/edit
You would still only be editing Caption 10. The routes then become redundant. We can write this simply as
/captions/10/edit
However, using an index action nested under a member route means "show me the captions for this media", so that needs to be explicitly stated:
/medias/1/captions
Or for actions such as new or create, we're saying "make a caption for this media". Again we need a specific route.
/medias/1/captions/new
And from the looks of things, you could be running into quite a bit of issues with pluralization. I would consider either adjusting your routes to fit convention, or devise a new standard for nomenclature.

Related

Rails ActiveAdmin has_one and belongs_to causes 'Undefined Method for <PLURAL_RESOURCE>'

In Rails, I have a 'User' model and a 'Wallet' model. A 'User' has_one wallet and each 'Wallet' belongs_to a 'User'. I made a 'Show' page in ActiveAdmin to view a User's Wallet. However, going to that page returns this error:
undefined method `wallets' for #<User:0x007f...>
HOWEVER, when I update the User model to 'has_many :wallets' instead of ':has_one wallet', everything works. Here is the relevant code from my models and ActiveAdmin code:
Models:
class User < ActiveRecord::Base
has_one :wallet, dependent: :destroy
end
class Wallet < ActiveRecord::Base
belongs_to :user
end
ActiveAdmin:
ActiveAdmin.register Wallet do
belongs_to :user
actions :all, except: :destroy
show do
div do
'hello'
end
end
end
ActiveAdmin.register User do
actions :all, except: :destroy
permit_params
action_item :wallet, only: :show do
link_to('Wallet', admin_user_wallet_path(user, user.wallet.id))
end
index do...
end
Any ideas as to where I might have gone wrong?
Edit 1: updates to correct colon placement mistakes in description
Edit 2:
in response to:
Can you show your routes file? Also, can you give us the full traceback of the error message and give us the output of rake routes? I suspect that the reason it's complaining about wallets not being defined (even though you never call wallets in the above code) is that some routing is making assumptions about how the relationships look. – Glyoko 4 mins ago
My routes file contains no mention 'wallet' or 'wallets'.
My stack error more specifically looks like this:
activemodel (4.1.15) lib/active_model/attribute_methods.rb, line 435
Let me know if you need more than that.
Here's the related output from 'bin/rake routes':
admin_user_wallets GET /admin/users/:user_id/wallets(.:format) admin/wallets#index
POST /admin/users/:user_id/wallets(.:format) admin/wallets#create
new_admin_user_wallet GET /admin/users/:user_id/wallets/new(.:format) admin/wallets#new
edit_admin_user_wallet GET /admin/users/:user_id/wallets/:id/edit(.:format) admin/wallets#edit
admin_user_wallet GET
/admin/users/:user_id/wallets/:id(.:format) admin/wallets#show
admin_user_wallet PATCH /admin/users/:user_id/wallets/:id(.:format) admin/wallets#update
admin_user_wallet PUT /admin/users/:user_id/wallets/:id(.:format) admin/wallets#update
ActiveAdmin uses InheritedResources gem internally, the belongs_to method ends up inside InheritedResources.
Possible solution here
ActiveAdmin.register Wallet do
belongs_to :user, singleton: true
actions :all, except: :destroy
end
The option singleton: true makes the Wallet a singular resource for the User.
Probably, another option optional: true may be helpful if Wallet is not required for any User to present
Even though your routes may not explicitly reference wallet(s), there may be something there making assumptions about how records are related to each other.
Look at the output of rake routes, in particular:
admin_user_wallet GET
/admin/users/:user_id/wallets/:id(.:format) admin/wallets#show
When you call admin_user_wallet_path(user, user.wallet.id) it's matching the /admin/users/:user_id/wallets/:id(.:format) route. Notice how that expects both a user id and a wallet id in the path. This is a tip-off that something is off here, since if you have the user, there should be exactly one wallet associated with it. You shouldn't need to give both the user and wallet id.
Since the wallet resource is nested under users, the page where you view the user's wallet is actually more of an index page than a show. If the wallet were an independent resource, then you could have a path like /admin/wallets/:id and things would work out fine.
But since the wallet is a subresource of the user, you would ideally want a path like /admin/users/:user_id/wallet. There's no need to pass the wallet id, since you already have the user.
tl;dr: Try changing the shows to indexs and see where that gets you. e.g.
index do
div do
'hello'
end
end
# ...
action_item :wallet, only: :index do
link_to('Wallet', admin_user_wallets_path(user))
end
Okay.. So I had this same exact issue. I had a belongs_to a parent where the parent had only a has_one to the child model..... Nothing seemed to work so I decided to fake it. I am not sure if this is the best way to do this but it worked. In the parent model, add a method:
class User < ActiveRecord::Base
has_one :wallet
def wallets
Wallet.where(user_id: id)
end
end
The above code is a hotfix until I can find some other way to implement what I need.

form_for nested resource with changed name

I have two models:
class Project < ActiveRecord::Base
has_many :stories, class_name: 'ProjectStory'
end
class ProjectStory
belongs_to :project
end
with the following routes:
resources :projects do
resources :stories
end
All the controllers are set correctly. Now, I have a stories/_form partial (which is to be reused for both new and edit) with the following line:
= form_for [#story.project, #story] do
The problem is however, that #story.model_name.singular_route_name returns project_story and hence it is trying to execute project_project_name_path to calculate form url. This, obviously, raising no method error.
I could override it with url option, however I would need to use ternary operator to handle both edit and new cases:
= form_for #story, url: #story.new_record? ? project_stories_path(#story.project) : project_story_path(#story.project, #story)
Is there any way to let rails know that it should use different name for a single element of the nested route? I would prefer not to rename my models or controllers (to avoid those annoying url helpers like edit_project_project_story_path)

Self nesting rails categories

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

Generated scaffold's view links are not corresponding correctly to the routes map

I have a problem with generated view paths. My routes.rb looks like following
Project::Application.routes.draw do
resources :project_templates do
resources :awards
end
...
project_template.rb like this
class ProjectTemplate < ActiveRecord::Base
belongs_to :user
has_many :awards #...
attr_accessible :user_id #...
...
award.rb like
class Award < ActiveRecord::Base
belongs_to :project_template
attr_accessible :tier #..
...
And generated view links are like this: awards_path
This way app does not work and I need to replace all with project_template_awards_path
I don't know why generator did this without project_template prefix but I ask for you to help me find a way to get around this. Maybe there is some generator command that will add up the missing suffixes to paths? I have to do the same with another class requirement.rb and there are views for that too so I hope there is some magic command for solving my issue.
rake routes | grep awards gives following output:
project_template_awards GET /project_templates/:project_template_id/awards(.:format) awards#index
POST /project_templates/:project_template_id/awards(.:format) awards#create
new_project_template_award GET /project_templates/:project_template_id/awards/new(.:format) awards#new
edit_project_template_award GET /project_templates/:project_template_id/awards/:id/edit(.:format) awards#edit
project_template_award GET /project_templates/:project_template_id/awards/:id(.:format) awards#show
PUT /project_templates/:project_template_id/awards/:id(.:format) awards#update
DELETE /project_templates/:project_template_id/awards/:id(.:format) awards#destroy
Could you try typing $ rake routes in project directory and post there output? Does it still show awards_path for Awards resources?
PS. Controllers have nothing to do here, as you can create route resources without corresponding controllers in app/.
The routes generated are correct. If you want to have this helpers, you can either add this to your route.rb
Project::Application.routes.draw do
resources :project_templates
resources :awards
end
Or implement helpers like
def awards_path(award)
project_template_awards(awards.project_template, awards)
end
More info on ruby guides

Rails routes.rb syntax

I have searched and searched and I cannot find a page which spells out the syntax of routes.rb in Rails 3. There are guidelines, overviews, even advanced examples but why isn't there a page that spells out the exact syntax of each keyword?? This page
http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/
contains a lot of advanced examples but doesn't take the time to discuss the behavior of all the examples given. I would appreciate it if someone could point me to a page that breaks down the syntax of routes.rb.
Here is the problem I am trying to solve. I have two models modelA and modelB. The relationship is modelA has_many modelB and modelB belongs_to modelA. I created the controller for modelB under namespace of modelA. So in my rails app folder, I have
app/controllers/modelA_controller.rb
app/controllers/modelA/modelB_controller.rb
I want my routes to be as such:
http://localhost:3000/modelA/:modelA_id/modelB/ [index]
http://localhost:3000/modelA/:modelA_id/modelB/:modelB_id [show]
etc.
I tried the following in routes.rb and none of it works:
resources :modelA do
resources :modelB
end
--
resources :modelA do
member do
resources :modelB
end
end
--
namespace :modelA do
resources :modelB
end
--
match '/modelA/:modelA_id/modelB/action', :to => '/modelA/modelB#action'
I know some of the things I tried are obviously wrong but when you have spent 2 days on a single problem, anything goes!
The reason no one has a "definitive" guide on routing syntax is that it is pretty flexible so you could probably write a few chapters just on that one subject. However, I would recommend: http://guides.rubyonrails.org/routing.html
From your question, it sounds like you're namespacing modelB under modelA but you also want the id for modelA to be within the route itself.
So if your ModelBController looks something like:
class ModelA::ModelBController < ApplicationController
# controller code here
end
then you can just do:
resources :modelA do
resources :modelB, :module => :modelA
end
However, are you sure you want to namespace the controller like that? If you just want nested resources like a typical has_many relationship, you don't need to be namespacing modelB under modelA.
Instead, you'd have:
/app
/controllers
/modelA
# some files
/modelB
# some files
And your modelB controller would be:
class ModelBController < ApplicationController
# controller code here
end
Then you could do
resources :modelA do
resources :modelB
end

Resources