When I click the 'New Schedule Status' button on the 'Project' show page, but the route that the error shows me is plural, when it should be singular. Here's my code:
# project.rb
class Project < ActiveRecord::Base
has_one :schedule_status
end
# schedule_status.rb
class ScheduleStatus < ActiveRecord::Base
belongs_to :project
end
# schedule_statuses_controller.rb
def new
#project = Project.find(params[:project_id])
#schedule_status = #project.build_schedule_status
end
# routes.rb
resources :projects do
resource :schedule_status
end
# _form.html.erb
<%= form_for [#project, #schedule_status] do |f| %>
...
The error informs me that my form_for line is incorrect. It seems like my instance variables are setup correctly, but the error is:
undefined method `project_schedule_statuses_path` for ...
Any idea why the route it's attempting to access is plural?
This is a bug. form_for looks for the plural version of the object. However since you've declared a singular resource :schedule_status, the path helper method is never created.
To get around this you should use :url parameter for form_for.
Look at this question/answer for more clarity.
It is not a bug it is a feature (ticket closed as won't fix):
rails issue:
https://github.com/rails/rails/issues/1769
summary quote:
the error has been around for a long while however a clean solution
doesn't readily represent itself. The polymorphic_url helper has no
'intelligence' in how it operates - it has no information about what
you've declared as resources in your routes.rb. All it has to go on it
the name of the model and how that maps to the named url helpers.
The problem is there is no easy way to discern whether a model maps
to a singular or a regular resource url. Checking for the presence of
a collection url doesn't work as the resource may have been specified
with :except => :index and trying to rescue route generation errors
doesn't work because passing an instance to a singular resource url
helper will generate a url with the format set to the id and no
exception.
rails issue closed in favour of the previous:
https://github.com/rails/rails/issues/4978
conclusion:
in such cases you're supposed to give the url. url_for cannot reflect
on routes to see if that's a resource or not.
Related
I have a Consultant model, where i have multiple consultant types (lawyers, doctors, psychologists etc.) listed on different websites, all handled from the same rails project.
I would like to have the consultant type as a part of the url, but have a hard time figuring out how, since it is dynamic based on domain/consultant type.
I am hoping for a solution to do a standard link:
=link_to consultant.name, consultant
without any specific link-config, so I can re-use templates across multiple consultant-websites.
Urls should be like this:
a-domain.com/doctor/doctor-name
b-domain.com/lawyer/lawyer-name
What I've tried so far, and used in the domain-specific templates (i know it is an ugly solution):
routes.rb
get 'lawyer/:slug' => 'consultants#show', as: :lawyer_consultant
get 'doctor/:slug' => 'consultants#show', as: :doctor_consultant
_consultant.html.haml for a-domain.com
= link_to consultant.name, lawyer_consultant_path(consultant)
I know the easy solution would just be this;
get 'consultant/:slug' => 'consultants#show', as: :consultant
But i want the url to be specific.
And the constraints: {host: a-domain.com} unfortunately does not allow for domain-specific routing, since only one as: :consultant can exist in routes.rb.
Routes don't actually have anything to do with your models. Your routes are the external REST API of your application while your models are an internal implementation detail.
I would just set the routes up as:
resources :doctors,
:lawyers,
only: :index
This just describes RESTful resources in your application like any other. Your routes should neither know or care that a doctor is a kind of consultant - its just a thing that can be routed to. Nor should it care that you're using slugs, to the router :id is just some kind of identifier.
The only actual connection between routes and models are the polymorphic routing helpers which basically just look up the the name of routing helper method to call based on convention over configuration:
irb(main):005:0> app.polymorphic_path("doctors") # doctors_path
=> "/doctors"
irb(main):006:0> app.polymorphic_path("doctor", id: 1) # doctor_path(1)
=> "/doctors/1"
irb(main):006:0> app.polymorphic_path(Doctor.new) # doctors_path
=> "/doctors"
irb(main):006:0> app.polymorphic_path(Doctor.find(1)) # doctor_path(1)
=> "/doctors/1"
When you pass a model instance Rails 'calls model_name.route_key on the model instance and then will determine if its singular or plural by checking if the model has been persisted.
If you want the polymorphic routing helpers to "just work" one solution is using Single Table Inheritance:
class AddTypeToConsultants < ActiveRecord::Migration[6.1]
def change
add_column :consultants, :type, :string
end
end
class Doctor < Consultant
end
class Lawyer < Consultant
end
When generating links you won't actually have to care about the type:
<%= link_to consultant.name, consultant %>
When you pass an instance of Doctor it will use doctor_path and when you pass an instance of Lawyer you get lawyer_path.
It also works for forms:
<%= form_with model: consultant do |f| %>
# ...
<% end %>
You can also acheive the same thing with the decorator pattern if STI isn't your cup of tea.
class DoctorDecorator < SimpleDelegator
def to_model
self
end
def model_name
ActiveModel::Name.new(Consultant, nil, "Doctor")
end
end
doctor = DoctorDecorator.new(Consultant.find(1))
polymorphic_path(doctor) # /doctors/1
I have a legacy database with a composite primary key. I have successfully added the composite_primary_keys gem to the project, and can retrieve, and create new records for the object in question. The operations to update, delete or show a single record are not working, and failing with an error of
'No route matches [GET] "/estimates/4.0,Test,A,0.0'
The class is defined as:
class Estimate < ActiveRecord::Base
self.table_name = 'estimate'
self.primary_keys = [:es_gsu_id, :es_loc_name, es_blh_flag, :es_version_id]
end
And the routes.rb has been modified with:
Rails.application.routes.draw do
constraints(:id => /\w+(,\w+)*/) do
resources :estimates
end
resources :estimates
end
Which I thought for Rails 4 would have it configured correctly. I can retrieve all and create new as noted, but the single access which actually relies on specifying the PK fields is failing on the routing.
Can anyone see what I'm doing wrong or what I've missed?
I was facing the exact same problem and after lots of tests found the following solution:
First of all, since you are deviating from default 'id' primary key assigned by Rails, you need to add this to your routes.rb:
resources :estimates, param: :es_gsu_id
Just mentioning one of the attributes of your composite key in 'param' hash will do.
Secondly, you don't need this piece of code
Rails.application.routes.draw do
constraints(:id => /\w+(,\w+)*/) do
resources :estimates
end
Now, your GET request to /estimates/4.0,Test,A,0.0 will be routed to 'show' action of 'estimates' controller where you can retrieve the resource using 'find' method like this:
resource ||= Estimate.find(params[:es_gsu_id])
params[:es_gsu_id] will have the value '4.0,Test,A,0.0' which will be used by the 'find' method to search for a unique record matching this composite key.
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)
In my app I have a User model which defines a history method that returns a list of Activity objects, showing the last N actions the user has carried out. The UserController#history method wires this with a view.
The code looks as follows:
class UserController < ApplicationController
def history
user = User.find(params[:id])
#history = user.history(20)
end
end
class User < ActiveRecord::Base
has_many :activities
def history(limit)
...
end
end
Naturally, I also added this line to my routes.rb file:
match '/user/:id/:action', :controller => 'user'
so now when I go to localhost:3000/user/8/history I see the history of user 8. Everything works fine.
Being a Rails NOOB I was wondering whether there is some canned solution for this situation which can simplify the code. I mean, if /user/8 is the RESTful way for accessing the page of User 8, is it possible to tell Rails that /user/8/history should show the data returned by invoking history() on User 8?
First of all the convention to name controllers is in the plural form unless it is only for a single resource, for example a session.
About the routes I believe you used the resources "helper" in your routes, what you can do is specify that the resource routes to users also has a member action to get the history like this
resources :users do
member do
get :history
end
end
I think there is no cleaner way to do this
You can check it here http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
As far as the rails standards are concerned, it is the correct way to show the history in your case. In rails controllers are suppose to be middle-ware of views and model, so defining an action history seems good to me.
And you can specify the routes in better way as:
resources :user do
get 'history', :on => :member #it will generate users/:id/history as url.
end
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"