Has_many through association form creation - ruby-on-rails

I have a has_many through association and I need to create a new record for UserGroup where the UserGroup is created within the Group controller.
In my groups controller I have this method
def add
#usergroup = UserGroup.new
end
Here is my UserGroup.rb model
class UserGroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
In the add.html.erb view I have it render the form whos code is listed below.
<%= form_for(#usergroup) do |form| %>
<div class="field">
<%= form.label :user_id %>
<%= form.text_field :user_id %>
</div>
<% form.text_field :group_id, value: #usergroup.id %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
However I am getting this error.
undefined methoduser_groups_path' for #<#:0x007fba7c11aab0>`
Can you not create a record this way? I want to have the url presented like this http://localhost:3000/groups/4/add.
Im somewhat new to Rails and forms for different models is where I get confused. Can anybody help me out with this?
Routes
Rails.application.routes.draw do
resources :maps
devise_for :users
resources :groups do
get 'add', on: :member
end
resources :rows
root to: 'maps#index'
end

First of all you have to declare an appropriate route. You going to post but set get 'add'.
post '/groups/:id/add'
resources :groups
Because of this you have get add_group_path instead of post add_groups_path
It'll be better to specify explicity method and url:
<%= form_for #usergroup, method: 'post', url: add_groups_path do |form| %>
And don't forget to permit params in controller

Related

Ruby on Rails Nested Models not Updating with form_for, but no error on update

So in my rails project, I have a Patient class, which has one Treatment class. This treatment class then has many DrNotes inside of it. I am still fairly new to rails, and I am aware that nesting this deeply is not recommended in Rails, but I am proceeding with this method.
My problem is with the editing of DrNotes. Since there are many doctor notes within treatment, I am trying to only edit one specific note. I am using Form_for to pass parameters to the doctor's note. When I submit the form, it redirects me to the page that should be shown only when the update function has succeeded. However, none of the notes are actually updated, and no errors are thrown when I try to perform the update.
Here are the models in question:
patient.rb
class Patient < ApplicationRecord
has_one :treatment, dependent: :destroy
accepts_nested_attributes_for :treatment, update_only: true
end
treatment.rb
class Treatment < ApplicationRecord
belongs_to :patient
has_many :dr_notes, class_name: "DrNote",
foreign_key: "treatment_id", dependent: :destroy
accepts_nested_attributes_for :dr_notes
end
dr_note.rb
class DrNote < ApplicationRecord
belongs_to :treatment
end
In my controller I have:
Doctor Note Edit Function
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
Doctor Note Update Function
def update_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
if #dr_note.update(dr_note_params)
redirect_to page_path(#patient)
else
flash.now[:error] = "Cannot update Doctor's notes"
render 'edit_dr_note'
end
end
Doctor Note Params
def dr_note_params
params.require(:dr_note).permit(:id, :name, :message)
end
I have :id in the params.permit because from researching, I heard that you need to include it when updating models, but i'm not sure if it is needed here.
I have the following code in the routes.rb
get '/pages/:patient_id/treatment/edit/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/update/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And in the edit_dr_note.html.erb
<%= form_for #patient.treatment.dr_notes.find(params[:dr_id]), url: update_dr_note_path do |patient_form| %>
<% #patient.treatment.dr_notes.each do |doctor| %>
<% if doctor.id == #dr_note.id %> #Only displays the fields for the desired note
<%= patient_form.fields_for :dr_note, doctor do |doctor_fields| %>
Name: <%= doctor_fields.text_field :name %>
Message: <%= doctor_fields.text_field :message %>
<% end %>
<p>
<%= patient_form.submit %>
</p>
<% end %>
<% end %>
<% end %>
Any help would be greatly appreciated. Thanks!
You are mixing two approaches(the nested resources and the nested attributes). Use one to serve your purpose.
With the nested resources:
<%= form_for [:pages, #patient, #treatment, #dr_note], url: update_dr_note_path do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_note.text_field :message %>
<p>
<%= dr_note.submit %>
</p>
<% end %>
The routes would be
get '/pages/:patient_id/treatment/:treatment_id/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/:treatment_id/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
Edit the edit_dr_note to define #treatment
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#treatment = #patient.treatment
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
And finally remove accepts_nested_attribute_for from the models, you don't need it in this approach.
With the nested attributes:
Keep the accepts_nested_attributes_for in the models. And change the routes and form like below
get '/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And the form_for
<%= form_for #patient, url: update_dr_note_path do |patient| %>
<%= patient.fields_for :treatment do |t| %>
<%= t.fields_for :dr_notes, #dr_note do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_notetext_field :message %>
<% end %>
<% end %>
<p>
<%= patient.submit %>
</p>
<% end %>
And change the dr_note_params method as below
def dr_note_params
params.require(:patient).permit(:id, treatment_attributes: [:id, dr_notes_attributes: [:id, :name, :message])
end
When you write the following line, you're trying to find a DrNote using the dr_id:
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
Whereas the dr_notes relation on Treatment does not seem to define any particular behavior, and this is your problem.
You'll need to find_by doctor's id (or dr_id in your code) and thus first define the relation on DrNote.

How to update child record in nested form in rails

How would I update a collection of child 'Product' rows when submitting the form below.
Many thanks
class User < ActiveRecord::Base
has_many :products, :class_name => 'Product', :inverse_of => :user
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
belongs_to :user, :inverse_of => :products
end
The View
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :products do |builder| %>
<% builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is somewhat minimal update action:
# app/controller/users_controller.rb
def update
# Get the user in param
#user = User.find(params[:id])
# Update attributes
if #user.update_attribute(params[:user])
# Redirect to :show for this user.
redirect_to :show
else
# update_attriubtes failed. Render the edit view.
render :edit
end
end
Secondly, you also need to ensure the attributes can be mass assigned such that the call to #user.update_attributes succeeds. Since you've not tagged your question with the appropriate Rails version, following is what you can do for Rails 3 and Rails 4:
Rails 3 - Define each attribute you want to mass assign as attr_accessible
Rails 4 - Permit parameters in controllers params.require(:user).permit(...).
Note that in your view you are not outputting the result of the helper text_field on this line:
<% builder.text_field :name %>
You need to use <%=...%> (note the equals = sign) in order to output the result of expressions within <%=...%>. This might have just been a typo here as you've done this correctly on all other elements. Replace the line with:
<%= builder.text_field :name %>
Also, suggest reading the "Action Controller Overview" of guides for details on Controllers.

has_many Custom Create Action

I think I need to user :through somewhere in my associations, but im very new to this so any help would be appreciated.
For simplicity, lets say I have 3 models.
Faults
Users
FaultComments
Where
Faults - belong_to :user and has_many :fault_comments
Users - has_many :faults and has_many :fault_comments
FaultComments - belongs_to :fault and belongs_to: user
What i would like to do is the ability to add fault comments from the fault show page, currently I have the below but i cant get it all to work as it should.
routes.rb
devise_for :users do
get '/users/sign_out' => 'devise/sessions#destroy'
end
resources :faults
resources :fault_comments
views/faults/show.html.erb
<h3>Add New</h3>
<%= form_for #faultcomment, :url => fault_comments_path(:fault_id => #fault.id, :user_id => current_user.id) do |f| %>
<%= f.text_field :comment %>
<%= f.submit %>
<% end %>
controllers/faults_comments_controller.rb
def create
#fault = Fault.find(params[:fault_id])
#faultcomment = #fault.fault_comments.new(params[:faultcomment])
#faultcomment.user_id = params[:user_id]
#faultcomment.comment = :comment
if #faultcomment.save
redirect_to faults_path
end
end
First of all, I think you should probably leave the FaultCommentsController like it came, which was probably something like this:
def create
#fault_comment = FaultComment.new(params[:fault_comment])
#fault_comment.user = current_user
if #fault_comment.save
redirect_to faults_path
end
end
(As a side note, it would probably be worth your while to learn about CamelCase and snake_case and how to properly translate between the two. The snake_case corollary to FaultComment is fault_comment, not faultcomment. You will certainly run into problems if you don't understand this.)
Your form on views/faults/show.html.erb looks more or less right to me. If you change your controller back to the original, does it work?
Also, change your form like this:
<h3>Add New</h3>
<%= form_for #fault_comment, :url => fault_comments_path do |f| %>
<%= f.text_field :comment %>
<%= f.hidden_field :fault_id, #fault.id %>
<%= f.submit %>
<% end %>

collection_select not filtering (in nested_form w/nested resources)

I've got an application that uses nested resources (see routes.rb below) to completely segregate users. It works great until I use collection_select to allow users to select objects from other models. For example, if I visit the store index view as user A, I only see stores created by user A. However, if I visit the store_group view and try to select a store to add to the group from the collection_select menu under the fields_for :store_group_details, I see all stores created by all users.
As far as I can tell, the problem might happen because there is no filter for stores in the store_group controller. store_group_details doesn't have a controller, but from what I've read, that seems to be correct since the model can only be accessed through a nested form in the store_group view. I have another situation where another view for another resource has several collection_select menus for selecting objects from other models, and all of those have the same problem (they display all objects in that model, regardless of which user created them).
How can I filter the objects shown in the collection_select menus? Is it a problem with what I'm passing into collection_select, or is it because the controllers don't do anything to filter the other models before those models' objects are displayed? I've looked at the docs for collection_select, and couldn't make it work based on that.
Thanks for your help, I've spent quite a bit of time trying to get this to work.
user.rb
class User < ActiveRecord::Base
has_many :store_groups
has_many :stores
has_many :store_group_details
end
store.rb
class Store < ActiveRecord::Base
belongs_to :user
has_many :store_group_details
has_many :store_groups, :through => :store_group_details
end
store_group.rb
class StoreGroup < ActiveRecord::Base
belongs_to :user
has_many :store_group_details, :inverse_of => :store_group
has_many :stores, :through => :store_group_details
accepts_nested_attributes_for :store_group_details
attr_accessible :store_group_details_attributes
end
store_group_detail.rb
class StoreGroupDetail < ActiveRecord::Base
belongs_to :store
belongs_to :store_group
belongs_to :user
attr_accessible :store_id
delegate :store_name, :to => :store
end
_store_group_form.html.erb
<div class="container">
<div class="span8">
<%= nested_form_for([#user, #store_group]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label "Store Group Name (required)" %>
<%= f.text_field :store_group_name %>
<%= f.label "Store Group Description" %>
<%= f.text_area :store_group_description %>
<%= f.fields_for :store_group_details %>
<p><%= f.link_to_add "Add store to group", :store_group_details %></p>
<br>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
_store_group_detail_fields.html.erb
<p>
<%= f.label "Select Store:" %>
<%= f.collection_select :store_id, Store.order(:store_name),
:id, :store_name, include_blank: true %>
<%= f.link_to_remove "remove" %>
</p>
routes.rb
resources :users do
resources :stores
resources :store_groups
resources :store_group_details
end
There must be a problem with your controller. Did you ever solve this issue?
Look at this answer as it might lead to a solution. Or add you stores_controller.rb.

How do I use nested attributes with the devise model

I have the same issue as Creating an additional related model with Devise (which has no answer).
I have overridden the devise view for creating a new user and added a company name, I have changed the model to use accepts_nested_attributes_for
There are no errors, but it is not adding the nested record and I don't have a controller where I can modify the request.
I have the following (shortened to make it readable):
routes.rb
map.devise_for :users
map.resources :users, :has_many => :companies
user.rb
has_many :companies
accepts_nested_attributes_for :companies
devise :registerable ... etc
company.rb
belongs_to :user
new.html.erb
...
<% form_for resource_name, resource, :url => registration_path(resource_name) do |f| %>
...
<% f.fields_for :company do |company_form| %>
<p><%= company_form.label :name %></p>
<p><%= company_form.text_field :name %></p>
<% end %>
...
UPDATE:
I didn't add :company to the attr_accessible list in the User model.
You can add the following method to the User model:
user.rb
def with_company
self.companies.build
self
end
And modify the view:
new.html.erb
...
<% form_for [resource_name, resource.with_company], :url => registration_path(resource_name) do |f| %>
...
<% f.fields_for :company do |company_form| %>
...
<% end %>
This way, you'll have the nested form to add one company to the user.
To dynamically add multiple companies to the user, check the Railcast #197 by Ryan Bates.
Make sure you are passing the pair of resources as an array, otherwide you will get an error like this: "wrong number of arguments (3 for 2)".
You may be trying to mass assign some protected variable, OR you might not be saving a valid record. Check to make sure that the record is actually saving to the db.
I realised this is a very old thread, but since I found a better solution, hence the reply.
Just change the new.html.erb as follows,
<% form_for(resource, :as => resource_name,:url => registration_path(resource_name) do |f| %>
...
<% prefix = "user[company_attributes]"%>
<% fields_for prefix, #user.company do |company_form| %>
...
<% end %>
<% end %>
This way when #user.save gets invoked, it would run company.save too with all the validations you may have in company model.
I don't have whole lot of RoR experience, but I think this is a better solution. What do you think?

Resources