I'm facing a problem with a nested route in Rails an I can't figure out, what I'm doing wrong.
In a nutshell: I use Devise for authentication an registration. Now I want to have the user enter more detailed information about his contact data. To keep the user model small, I want to use a different model called account. As a user only will have one account, I use a on-to-one association and a nested route. But somehow the routing does not work.
This is my user model:
user.rb
class User < ActiveRecord::Base
has_one :account, :dependent => :destroy
attr_accessible :username, :email, :password, :password_confirmation, :remember_me
and this is my account model:
account.rb
class Account < ActiveRecord::Base
attr_accessible :anrede, :land, :nachname, :plz, :stadt, :strasse, :user_id, :vorname
belongs_to :user
end
and my route file looks like this:
routes.rb
devise_for :users, :path => 'members'
resources :users do
resource :account
end
As a user might not have a account yet, I test this in my view:
<% if current_user.account.try %>
<li><%= link_to "Account", user_account_path %></li>
<% else %>
<li><%= link_to "create Account", new_user_account_path %></li>
<% end %>
but when I enter the root path with a signed in user, Rails tells me
Routing Error
No route matches {:action=>"new", :controller=>"accounts"}
but there is a new action in my accounts_controller.rb as I have scaffolded the whole CRUD set (edited create with current_user.build_account) and it's also the path given by rake routes.
I'm desperately stuck in this! Could anybody help me, please?
EDIT
This is the output of my rake routes:
user_account POST /users/:user_id/account(.:format) accounts#create
new_user_account GET /users/:user_id/account/new(.:format) accounts#new
edit_user_account GET /users/:user_id/account/edit(.:format) accounts#edit
GET /users/:user_id/account(.:format) accounts#show
PUT /users/:user_id/account(.:format) accounts#update
DELETE /users/:user_id/account(.:format) accounts#destroy
EDIT2
this is the error message for the action new form:
NoMethodError in Accounts#new
Showing /home/stonjarks/Work/toytrade_devise/app/views/accounts/_form.html.erb where line #1 raised:
undefined method `accounts_path' for #<#<Class:0xa44d0cc>:0xab6222c>
Extracted source (around line #1):
1: <%= form_for(#account) do |f| %>
2: <% if #account.errors.any? %>
3: <div id="error_explanation">
4: <h2><%= pluralize(#account.errors.count, "error") %> prohibited this account from being saved:</h2>
I solved this using this SO hack, but it's a strange behavior anyway:
Rails Nested Route For Singular Resource
<%= form_for #account,:url=>{:action=>:create}
But I still don't get the point of this routing anyway. Despite this, I can't manage to find a route to show the account:
/users/1/account
ActiveRecord::RecordNotFound in AccountsController#show
Couldn't find Account without an ID
new_user_account_path is missing a user instance which should be provided for example:
new_user_account_path(current_user)
If you look at the following line:
new_user_account GET /users/:user_id/account/new(.:format) accounts#new
You can see that the path demands a :user_id - a user instance.
You can read here for further clarification of your problem.
Related
Newbie Here!
I'm unsuccessfully trying to submit a form of a nested model.
Aim:
Verify a translation by submitting a boolean to a reviews model to associate whether a translation is verified or not, with translation entry and user model associated references.
# routes.rb
resources :entries do
resources :reviews
end
# entry.rb
belongs_to :user
has_one :review
accepts_nested_attributes_for :review
# user.rb
has_many :entries
has_many :reviews
# review.rb
belongs_to :user
belongs_to :entry
From entry index, pass the entry instance to partial, works perfect
# /entries/index.html.erb
<% #entries.each do |entry| %>
...
<%= render 'reviews/new', entry: entry %>
...
<% end %>
Unsuccessfully setting up new/create form.
What happens is that the entry instance is well received, but I am failing to create a new model instance for review belonging to entry.
entry.review raises an nil error for review, while entry is fine when testing through browser console
First argument in form cannot contain nil or be empty
# reviews/_new.html.erb
<span>
<%= form_for entry.review do |f| %>
<div class="form-check form-switch">
<%= f.check_box :verified, class: "form-check-input" %>
</div>
<%= f.submit class: "btn btn-primary"%>
<% end %>
</span>
Another attempt was also to use just #review from the controller but that doesn't obey nested routes.
My controller looks like this
# reviews_controller.rb
def create
#entry = Entry.find(params[:entry_id])
#review = #entry.review.build(review_params)
#review.user_id = current_user.id
#review.save
end
private
def review_params
params.require(:review).permit(:verified, user: current_user, entry: #entry)
end
Am I suppose to implement my actions in the entries_controller?
I have also found the tutorial here useful but replication was unsuccessful.
Another StackOverflow reference here
I still get the error entry.review.build that review is nil.
First argument in form cannot contain nil or be empty
When building an associated record over a has_one relation, instead of
#review = entry.review.build(review_params)
you need to use the following:
#review = entry.build_review(review_params)
See the documentation for more details.
Am I suppose to implement my actions in the entries_controller?
It depends on what you're after. If you have a dedicated form for adding a new review and it is not embedded in another form for creating or updating an entry then implementing a create action in ReviewsController is the straightforward solution – in this case you should also not need accepts_nested_attributes_for in Entry.
If, however, you want to be able to create or update an entry as well as its review using the same form, then you should nest the review form in the form of the entry, keep accepts_nested_attributes_for, and use actions in EntriesController. The documentation should get you started there.
I keep getting unpermitted parameters for my appointment model.
Here are my models
class Appointment < ApplicationRecord
belongs_to :client
belongs_to :trainer
end
class Trainer < ApplicationRecord
has_many :appointments
has_many :clients, through: :appointments
end
class Client < ApplicationRecord
has_many :appointments
has_many :trainers, through: :appointments
end
Here's my controller, I just listed my private method for sake of brevity.
def appt_params
params.require(:appointment).permit(:appointment_date, client_id: [],
trainer_id: [])
end
The error says unpermitted parameters for trainer, client.
Am I missing something in my strong parameters method?
Here is my appointments/new view
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer, Trainer.all, :id, :first_name %>
<%= f.collection_select :client, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
I added collection to my appt_params method and still getting the same error. I'm still getting the hang of Rails and any help would be appreciated, thanks!
Since you have used associations, only client_id and trainer_id is enough and those should of integer form not array.
So change your strong parameters method code to :
def appt_params
params.require(:appointment).permit(:appointment_date, :client_id,
:trainer_id)
end
And your applications/new view to:
<%= form_for #appointment do |f| %>
<%= f.datetime_select :appointment_date %>
<%= f.collection_select :trainer_id, Trainer.all, :id, :first_name %>
<%= f.collection_select :client_id, Client.all, :id, :name %>
<%= f.submit %>
<% end %>
Was running with the same issue as you and after hours of debugging I was able to solve it:
Was my first time trying associations in Rails with models similar to you, so most documentation online recommends to define the foreign key's as client_ids:[] assuming your ids were arrays, when definitely they are integers as my parameters where
Parameters: {"utf8"=>"✓", "authenticity_token"=>"LEcwQ56xYJGpq2zIs6Cz0YbU7B7mBKRa6rhspVIxo9vEB5/UoFUvHYiN0UC0krTiIp+d0tzhit6DZT1Z8PmYYg==", "califica"=>{"text"=>"hi", "grade"=>"5", "user_id"=>"1", "party_id"=>"1"}, "commit"=>"Create Calification"}
I think this is due to f.collection_select taking as expected one value. So after hours of using the permit of arrays such as :user_id => [] , user_id:[] I was getting always error of Unpermitted parameters.
Tried the answer of #Bharath (which is correct) but it was still not working, that's was when I realized that my old models weren't made with references (ActiveModel::UnknownAttributeError (unknown attribute 'user_id' for Calification.):) so I had to make a references migration to add a foreign key and then it was all working perfectly.
This could be the wrong way to go about this entirely, and I'm very open to alternatives.
I've got the following models, where Users can have many Positions:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :all_blank
end
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :position
end
class Position < ApplicationRecord
end
On my edit user form, I'd like to allow a User's current position to be updated. I do that in the following way:
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :user_positions, #user.user_positions.order(created_at: :desc).first do |ff| %>
<%= ff.hidden_field :user_id, value: #user.id %>
<%= ff.collection_select :position_id, Position.all, :id, :label %>
<% end %>
<%= f.submit "Update User" %>
<% end %>
The issue I'm running into is that a new instance of UserPosition is being created every time I submit the form, even if the Position that's selected hasn't changed. This leads to a bunch of duplicate entries in the join table, when I really only care about "promotions" or "demotions" when the value of position_id has changed.
I don't want to add a custom validator to disallow the creation, because I still want the form to be able to submit with an unchanged position. An example of this is when I only want to change the User's name.
Any advice on how to deal with this use case?
It turns out that you can actually use any method as a Symbol argument the to accepts_nested_attributes_for reject_if option.
I updated my User model as follows, and now the behavior is exactly what I want:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :same_as_previous_position
def same_as_previous_position(attributes)
if self.user_positions.empty?
return false
end
Position.find(attributes[:position_id]) == self.user_positions.order(created_at: :desc).first.position
end
end
I am building a Rails 4.2.4 app where I have Units and Medics. When I edit each unit I have two spots for the medics, incharge and attendant. I want some way to validate that both the incharge_id and attendant_id are not the same. That way I can't assign myself as both positions on the unit.
Here is what my model and form view looks like.
unit.rb
class Unit < ActiveRecord::Base
belongs_to :attendant, :foreign_key => :attendant_id, :class_name => 'Medic'
belongs_to :incharge, :foreign_key => :incharge_id, :class_name => 'Medic'
belongs_to :unit_status
end
medic.rb
class Medic < ActiveRecord::Base
has_many :units
end
units/_form.html.erb
<%= form_for(#unit) do |f| %>
<%= f.label 'Attendant'%>
<%= f.collection_select(:attendant_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'In Charge'%>
<%= f.collection_select(:incharge_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'Unit Status'%>
<%= f.collection_select(:unit_status_id, UnitStatus.order("status ASC"), :id, :status, {})%>
<%= f.submit "Update" %>
<% end %>
So in summary if I edit a unit and I accidentally assign the id of "1" to the unit, I want to error out and give some sort of message, "Cannot assign the same medic to both positions". Something like that.
The only thing I can think of, is to somehow filter the params in the controller saying if the params of attendant_id and incharge_id are are == then redirect to the edit_unit_path and display a flash message, "You cannot assign the same medic to both positions".
It seems like it would be better to do validations on the model side instead of stuffing logic in the controller, but I'm not sure how to simultaneous validate the two different columns for uniqueness.
I came up with this on the Unit model.
validate :attendant_and_incharge
def attendant_and_incharge
errors.add(:attendant_id, "can't be the same as the incharge") if attendant_id == incharge_id
end
This will not allow me to save the same id to the unit model for attendant_id and incharge_id. It fails silently and directs to the units_path. Just need to put some conditional in the controller to redirect to the edit path on failure. (ThumbsUp)
I am following the Rails Tutorial doing a little project for myself and then try to progress.
Well I am facing a problem for which I found the solution, but I'll really appreciate any thoughts and opinions because to make it work I have to disable a validation in an association.
The context is as follow : the application (big name for what it does actually...) has users to track their weight. I would like that when a user sign up, he/she enters a first measure on the go.
So here are the simplified models:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation, :measures_attributes
has_secure_password
has_many :measures, dependent: :destroy
accepts_nested_attributes_for :measures
# here goes validations before_save, etc. taken from the Rails Tutorial
end
Measure.rb
class Measure < ActiveRecord::Base
attr_accessible :weight
belongs_to :user
# This is kind where the problem is...
# If I deactivate the validation for user_id everyhing goes fine
validates :user_id, presence: true
validates :weight, presence: true, numericality: { greater_than: 0 }
default_scope order: 'measures.created_at ASC'
end
Here is the Users controller
class UsersController < ApplicationController
# GET /users/new
def new
#user = User.new
#user.measures.build
end
# POST /users
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Hi #{#user.name}. Welcome !"
redirect_to #user
else
render :new
end
end
end
Here is the form partial for the user :
<%= form_for(user) do |user_form| %>
<%= render 'shared/error_messages', object: user_form.object %>
<%= render 'users/fields', user_builder: user_form %>
<%= user_form.fields_for :measures do |measure_fields| %>
<%= render 'measures/fields', measure_builder: measure_fields, full: true %>
<% end %>
<%= user_form.submit submit_text, class: 'btn btn-large btn-primary' %>
<% end %>
And even when I fill the form correctly, I get this error :
* Measures user can't be blank
The only way I found to make it work is to get rid of the :user_id validation for presence in the Measure model. I want to emphasize that when this validation is off, the user is saved, the measure is saved and correctly associated with the newly created user.
Am I doing something wrong ? Is the :user_id presence validation in the Measure model really useful (it is in the Rails Tutorial and it makes perfectly sense for me) ? If yes why is the Measure validation failing when it is on ?
Thanks a lot in advance.
You could try validates :user instead of user_id. Then it might work out that the two are associated in memory even though the User hasn't been saved yet.
I think I may have found an error in your code that would cause the error you're seeing.
In your new controller you specifically create an association:
#user.measures.build
In your create controller, you create a new element based on the fields that were passed in.
#user = User.new(params[:user])
There are some cases where the associated element will be dropped. I can't remember if this happens by default if it doesn't validate or if there was an option you need to set to make it drop.
I think you may need to add
#user.measure.build if #user.measures.empty?
in your create controller else clause before render :new.
I don't know if you're actually hitting this problem, but it would explain why the association to your User wasn't set.