Unable to extract username by ID that comes from another model - ruby-on-rails

Seems so simple, but this one makes me crazy by now:
I got a Topics table, which has an user_id that is written during the creation of a new topic, and comes from the User table.
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :topics
has_many :comments
validates_presence_of :password, :on => :create
validates_uniqueness_of :email
end
class Topic < ActiveRecord::Base
attr_accessible :active, :id, :opened_at, :title, :description
belongs_to :user
has_many :comments
end
Now the first thing I tried is writing the view like this:
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
Which drops:
undefined method `name' for nil:NilClass
Then I tried another approach:
topics_controller.rb
def index
#topics=Topic.all
#author=User.find(#topics.user_id)
end
But this goes like:
undefined method `user_id' for #<Array:0x3c46fb0>
(If I hardcode any number instead of #topics.user_id then it shows the given user's name properly).
Any help is appreciated.
PS.: This is the way I save the Topic:
def create
#topic = Topic.new(params[:topic])
#topic.active = true
#topic.user_id = session[:user_id]
if #topic.save
redirect_to topics_url
flash[:notice] = 'Success!'
else
render "new"
end
end
I guess the association is OK, because when I put
<%=h topic.user_id %>
then it shows the proper IDs. It's just that I cannot translate the ID to the user name that is stored in the User table.

Use the following in case topic.user is nil:
<% #topics.each do |topic| %>
<%= topic.user.try(:name) %>
<% end %>
You should also remove #author=User.find(#topics.user_id). This will raise an error because #topic is a collection of all the Topic instances and not a single instance (like Topic.all.first as an example).

You are using #topics.user_id, which is where things are going wrong; you are trying to call user_id on an array instead of a single Topic object. Use #pluck:
Controller Code:
#topics = Topic.all
#topics_ids = Topic.pluck(:user_id).compact #will remove nils
#authors = User.find(#topics_ids)
View Code:
<b>All Topics: </b>
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
<b>All Authors: </b>
<% #authors.each do |author| %>
<%= author.name %>
<% end %>

Related

Simple_form_for many to many with validation

Setup
I have a simple many to many relationship between a Submit and an Answer through SubmitAnswer.
Answers are grouped by a Question (in my case each question has three answers) - think of it as a multiple choice quiz.
I have been trying to use SimpleFormFor to make a form which renders a predetermined set of questions, where each question has a predetermined set of answers.
Something like this:
#form
<%= simple_form_for Submit.new, url: "/questionnaire" do |f| %>
<% #questions.each do |question| %>
<%= f.association :answers, collection: question.answers %>
<% end %>
<%= f.submit :done %>
<% end %>
#controller
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :new
end
end
def submit_params
params.require(:submit).permit(answer_ids: [])
end
When I submit the form, Rails creates the join table, SubmitAnswers, automatically.
So here is the crux of the matter: Whats the easiest way to re-render the form, errors and all, if not all questions have been answered, ie if #submit.answers.length != #question.length ?
I can add a custom error with errors.add(:answers, 'error here'), but when I re-render, the correctly selected answers arent repopulated, which is suboptimal.
For completions sacke, here are my models:
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
end
class SubmitAnswer < ApplicationRecord
belongs_to :submit
belongs_to :answer
end
class Answer < ApplicationRecord
has_many :submit_answers
has_many :submits, through: :submit_answers
end
Alright, after some digging we did find the answer to make the form work, albeit with more pain that we anticipated a simple many-to-many should take.
#model
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
accepts_nested_attributes_for :submit_answers
end
#controller
def new
#submit = Submit.new
#questions.count.times { #submit.submit_answers.build }
end
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :home
end
end
def submit_params
params.require(:submit).permit(submit_answers_attributes:[:answer_id])
end
#form
<%= simple_form_for #submit do |f| %>
<%= f.simple_fields_for :submit_answers do |sa| %>
<%= sa.input :answer_id, collection: #answers[sa.options[:child_index]], input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}, label: #questions[sa.options[:child_index]].name %>
<div class="invalid-feedback d-block">
<ul>
<% sa.object.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.submit :done %>
<% end %>
The solution is to use simple_fields_for/fields_for. Note that <%= sa.input :answer_id %> must be :answer_id, not :answer, which is something I had tried before.
Also one must allow accepts_nested_attributes_for :submit_answers, where :submit_answers is the join_table.
I prebuild my SubmitAnswers like so: #questions.count.times { #submit.submit_answers.build } which generates an input field for each question, all of which get saved on the form submit, a la build.
For the strong_params one needs to permit the incoming ids:
params.require(:submit).permit(submit_answers_attributes:[:answer_id]), so in this case submit_answers_attributes:[:answer_id].
For anyone wondering what the params look like:
{"authenticity_token"=>"[FILTERED]",
"submit"=>
{"submit_answers_attributes"=>
{"0"=>{"answer_id"=>""}, "1"=>{"answer_id"=>""}, "2"=>{"answer_id"=>""}, "3"=>{"answer_id"=>""}, "4"=>{"answer_id"=>""}, "5"=>{"answer_id"=>""}, "6"=>{"answer_id"=>""}}},
"commit"=>"done"}
As for the errors, im sure there might be a better way, but for now I have just manually added them with input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}.
On a final note, the sa.object # => SubmitAnswer allows me to retrieve the Model, the errors of that Model or whatever else one might want.

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.

Ruby on Rails: Add form for another model on a form

Within my Ruby on Rails application I am trying to implement a relationship between Group and Contact, whereby one group can contain many contacts and one contact can be part of many groups. I am using a model called Contactgroup to deal with this relationship, and so the tables are:
Group (id, name)
Contact (id, firstname, surname)
Contactgroup (group_id, contact_id)
With example data being:
Groups:
ID Name
1 Singers
2 Drummers
Contacts:
ID Firstname Surname
1 Freddy Mercury
2 Roger Taylor
3 Kurt Cobain
4 Dave Grohl
Contact Groups:
Group_ID Contact_ID
1 1
1 3
1 4
2 2
2 4
What I am trying to do is get it so that when a user creates a group, they can select the contacts that they want to add to that group. This means that there is the group form, whereby the user types the group name, and on this form I want to display checkboxes for each of the user's contacts so that the user can select the contacts they want to add to the group, and when they click submit the new group will be saved in the Group table and the new contact group records will be saved in the Contactgroup table.
This is the app/views/groups/_form.html.erb code:
<%= form_for #group do |f| %>
<% if #group.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#group.errors.count, "error") %> prohibited this group from being saved:
</h2>
<ul>
<% #group.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
On here you can see the code I am trying to use to do this:
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
I have got this from rails guides (http://guides.rubyonrails.org/getting_started.html) but I get the error undefined methodcontactgroups' for #` and don't think this will give me what I want.
My routes file is:
Rails.application.routes.draw do
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
resources :users
get 'welcome/index'
root 'welcome#index'
resources :contacts
resources :groups do
resources :contactgroups
end
resources :contactgroups
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
controller :sessions do
get 'login' => :new
post 'login' => :create
get 'logout' => :destroy
end
end
My groups_controller:
class GroupsController < ApplicationController
def index
#groups = Group.where(user_id: session[:user_id])
end
def show
#group = Group.find(params[:id])
#members = Contactgroup.where(group_id: #group.id)
end
def new
#group = Group.new
#contacts = Contact.where(user_id: session[:user_id])
end
def edit
#group = Group.find(params[:id])
end
def create
#group = Group.new(group_params)
#group.user_id = session[:user_id]
if #group.save
redirect_to #group
else
render 'new'
end
end
def update
#group = Group.find(params[:id])
if #group.update(group_params)
redirect_to #group
else
render 'edit'
end
end
def destroy
#group = Group.find(params[:id])
#group.destroy
redirect_to groups_path
end
private
def group_params
params.require(:group).permit(:name, :user_id)
end
end
And contactgroups_controller:
class ContactgroupsController < ApplicationController
def destroy
#contactgroup = Contactgroup.find(params[:id])
#contactgroup.destroy
redirect_to(:back)
end
end
My models are as follows:
Contact.rb:
class Contact < ActiveRecord::Base
end
Group.rb:
class Group < ActiveRecord::Base
end
Contactgroup.rb:
class Contactgroup < ActiveRecord::Base
belongs_to :contact
belongs_to :group
end
There must be a simple solution to solve this as I assume it is commonly done on other systems, but I am not sure how to do this.
Can someone please help.
You cannot use form inside form. The correct way to use collection_check_boxes is following.
Replace
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
With just
<p>
<%= f.collection_check_boxes(:contact_ids, #contacts, :id, :firstname) %>
</p>
This was much simpler than initially thought/suggested.
What I needed to do was change the models to:
Contactgroup
belongs_to :contact
belongs_to :group
Contact
has_many :contactgroups
has_many :groups, through: :contactgroups, :dependent => :destroy
Group
has_many :contactgroups
has_many :contacts, through: :contactgroups, :dependent => :destroy
In the groups_controller I needed to change the new method and params to:
def new
#group = Group.new
#group.contactgroups.build
end
private
def group_params
params.require(:group).permit(:name, :user_id, { contact_ids: [] })
end
And then add the following line of code into app/views/groups/_form.html.erb:
<%= f.collection_check_boxes :contact_ids, Contact.where(user_id: session[:user_id]), :id, :firstname ,{ prompt: "firstname" } %>
This provides me with a checkbox for each contact, and allows contactgroup records to be created from the group form.
Ok so the issue is very simple. You are calling #group.contactgroups but you haven't actually set up that association on the group model yet. only have associations set up from the contactgroup side. So you can do contactgroup.group but not group.contactgroups
Your best bet is to actually model this as habtm - as I mentioned earlier. This is how you'd do that:
Contact.rb:
class Contact < ActiveRecord::Base
has_and_belongs_to_many :groups
end
Group.rb:
class Group < ActiveRecord::Base
has_and_belongs_to_many :contacts
end
Note: you still have the concept of the contact-group for HABTM but using Rails standard naming it would be in your database as the contacts_groups table. Then you could build your forms that way.
With a quick google, here's a S/O question on using checkboxes with HABTM (haven't vetted it for usefulness to your situation): Rails 4 - checkboxes for has_and_belongs_to_many association
Using HABTM is Rails standard practice for lots of very good reasons. It really does actually fit your situation (honest!) and it does not actually break the requirement you have of wanting to see it in the SQL (seriously!).
Give it a try first :)
I can tell you how to break Rails conventions... but it's generally well-understood that you shouldn't break conventions until you know what the conventions are there for.

Nested attributes values are not returned on EDIT

When I edit a object, the relationship object values are not displayed in the edit form. The create on other hand is working.
Here are the models:
class LogFile < ActiveRecord::Base
has_one :config_file, dependent: :destroy
accepts_nested_attributes_for :config_file, allow_destroy: true
end
class ConfigFile < ActiveRecord::Base
belongs_to :log_file
end
and this is the controller:
# GET /log_files/1/edit
def edit
end
private
# Use callbacks to share common setup or constraints between actions.
def set_log_file
#log_file = LogFile.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def log_file_params
params.require(:log_file).permit(
:name,
:user_id,
config_file_attributes: [:id, :json, :_destroy]
)
end
The form looks like this:
<%= f.simple_fields_for :config_file_attributes do |n| %>
<%= n.input :json %>
<% end %>
I have firstly try to join or include the relationship model, but was not able to do it. Some of the folks said thar id in the permit() function do the trick, but nothing changes in my situation.
Could anyone advice what to try?
Also when I put the following code in the form template:
<%= debug #log_file %>
no details about the relationship model are returned.
This is the solution I have apply using the accepted answer:
<% if #log_file.id %>
<%= f.simple_fields_for :config_file do |n| %>
<%= n.input :json %>
<% end %>
<% else %>
<%= f.simple_fields_for :config_file_attributes do |n| %>
<%= n.input :json %>
<% end %>
<% end %>
Try using relation name without _attributes suffix:
<%= f.simple_fields_for :config_file do |n| %>
<%= n.input :json %>
<% end %>

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

Resources