OK so I've been at this for 6 plus hours and scoured every variation on this question, but nothing I do works!
I'm building a fairly simple family tree app for a friend. There are two models in question, Person and Relationship. Whenever you build a new Person (after the very first one which is handled separately), it should also build a Relationship which is essentially a join table that has a person_1 attribute, a person_2 attribute, and a relationship_type attribute such as "mother".
When I build a new person, that person is saved to the database just fine, but the associated relationship is not. I can see that all of the necessary params are being passed in, and I've read as much as I can about nested params, but no dice.
My code is a big old mess and I'm a beginner so please ignore any unrelated code weirdness.
My Person model (this centers around the out_relationships resource)
class Person < ApplicationRecord
has_many :out_relationships, :class_name => "Relationship", :foreign_key => "person_1_id"
has_many :in_relationships, :class_name => "Relationship", :foreign_key => "person_2_id"
belongs_to :user, optional: true
accepts_nested_attributes_for :out_relationships
end
My Relationship model:
class Relationship < ApplicationRecord
belongs_to :person_1, :class_name => "Person"
belongs_to :person_2, :class_name => "Person"
end
My form:
<%= form_for #person, :url => create_relative_path, html:{method:'post'} do |form| %>
<%= fields_for :out_relationships do |builder| %>
<div class="form-group">
<%= builder.label :relationship_type, "Relationship to #{#root_person.first_name}" %>
<%= builder.text_field :relationship_type, class: "form-control" %>
<%= builder.hidden_field :person_1, value: #person_1.id %>
<%= builder.hidden_field :person_2, value: #person_1.id %>
</div>
<% end %>
I set person_1 and person_2 to the same value just as a test. That's kind of unrelated to the problem, I think, and shouldn't affect what's happening. Anyways...
My controller:
def new
#root_person = Person.find(params[:id])
#person = Person.new
#user = current_user
#root_person.out_relationships.build
end
# GET /people/1/edit
def edit
end
def create
#root_person = Person.find(params[:id])
#person = Person.new(person_params)
#user = current_user
respond_to do |format|
if #person.save
format.html { redirect_to #person, notice: 'Person was successfully created.' }
format.json { render :show, status: :created, location: #person }
else
format.html { render :new }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
def person_params
params.require(:person).permit(:first_name, :middle_name, :last_name, :birth_date, :birth_city,
:birth_country, :current_city, :current_country, :profession, :maiden_name, :marital_status, :patel_type,
out_relationships_attributes: [:person_1, :person_2, :relationship_type])
end
Anyways, that's more of a mess than I even thought, thanks to anyone still reading, would love a nudge in the right direction!
Related
I have two models: Personand User. A person can have a single user or no user at all, but every user have to be belong to a person.
I have set both models like so:
Person (has a has_user attribute):
has_one :user, dependent: :destroy
accepts_nested_attributes_for :user
User (has person_id attribute):
belongs_to :person
validates_presence_of :login, :password
validates_uniqueness_of :login
In the Person form I have a nested form_for that only shows up if I check a check box that also set the has_user attribute to true on the Person's form part.
My issue is that whenever I submit a person that has no user, it still tries to validate the user. How can I make it work?
UPDATE:
In my view:
<div class="form-group">
<%= person_form.label :has_user, :class => 'inline-checkbox' do %>
Possui usuário
<%= person_form.check_box :has_user, {id: "hasUser", checked: #person.has_user} %>
<% end %>
</div>
</div>
<div id="userPart" class="findMe"
<% if #person.user.id.blank? %> style="display:none;"
<% end %>>
<h2> User: </h2>
<div class="container">
<%= person_form.fields_for :user do |user_form| %>
<%= render partial: 'users/campos_user', locals: {form: user_form} %>
<% end %>
</div>
</div>
In my Person.js:
jQuery ->
$(document).ready ->
$(document).on 'turbolinks:load', #Added line
console.log("Turbolinks ready.")
$("#hasUser").change ->
$("#userPart").toggle();
return
return
return
return
In my Person Controller:
def new
#person= Contato.new
#person.ativo = true
#page_title = 'Novo person'
#person.build_user
end
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save
flash[:notice] = 'Contato foi criado com sucesso.'
if #person.has_user == true
format.html {redirect_to controller: :users, action: :new, person_id: #person.id}
else
format.html {redirect_to #person}
end
format.json {render :show, status: :created, location: #person}
else
flash[:warn] = "Erro ao criar contato."
format.html {render :new}
format.json {render json: #person.errors, status: :unprocessable_entity}
end
end
end
Person's params:
def person_params
params.require(:person).permit(:id, :company_id,
:active, :name,
:cargo, :celular,
:email, :nascimento,
:observacoes, :mensagem_instantanea,
:tipo_msg_inst, :possui_usuario,
user_attributes: [:login, :password, :permits, :id, :person_id, :_destroy])
end
You can reject submitting user attributes by using reject_if option
accepts_nested_attributes_for :user, :reject_if => :no_user_connected
def no_user_connected(attributes)
attributes[:user_attributes][:login].blank?
end
This should discard the user part of the form (all user attributes). Please note that I dunno how your form or controller looks like, so you might need to build the condition differently.
You can also make the validations on user conditional. But overall the best way to avoid convoluted validation problems is to use some kind of form objects where you encapsulate the validations process. https://thoughtbot.com/blog/activemodel-form-objects
Don't know how to save a question form that will have 2 ids, event id and user id
User.rb
class User < ApplicationRecord
has_many :questions
has_many :answers
end
Event.rb
class Event < ApplicationRecord
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
belongs_to :user
belongs_to :event
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers
end
Answer.rb
class Answer < ApplicationRecord
belongs_to :user
belongs_to :question
scope :sorted, ->{ order(created_at: :asc) }
end
questions_controller.rb
def new
#question = current_user.questions.build
end
def create
#question = Question.new(question_params)
#question["user_id"] = current_user.id
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
I have the standard form generated with the scaffold, but I cant piece together what I am missing from my limited knowledge and experience in rails on how to get every question that a user makes to be linked to a specific created event and show which user created that particular question (I would assume that each question entry will need a user_id and a event_id column)
<%= form_with(model: question, local: true) do |form| %>
<%= form.label :body %>
<%= form.rich_text_area :body %>
<%= form.submit 'Create Question' %>
<% end %>
Updated for Error:
When I try to create a question, each entry requires an event_id (an event has many questions) and a user_id (I want to show who created that question).
Are my models and controllers setup correctly? When I try to create a question for an event, the error "Event must exist" occurs
Updated with ERD pic ( Not sure if I should just have users or seperate into creators and users )
ERD after reading up on last update
I begun reading up more on data modelling and I came up with this ERD...I am still not very sure on achieving 3NF and setting up the relationships, and how to translate it to rails models but would be great to have comments on my ERD so I can learn.
Creator creates events that users can join. Creator creates questions that users can post answers. Each event has many questions and each question can have many answers.
If I understood you correctly, you need nested resources to achieve your goal. It means, that questions are nested inside events, like 'parent' event has 'children' questions. At first, change your routes:
resources :events do
resources :questions
end
Run rake routes in terminal and you will see new routes with :event_id parameter. Now on events#index you can add link_to 'Questions about this event', event_questions_path(event) near each event, the link will lead you to events/1/questions (1 is id of the event). In QuestionsController you have new parameter, event_id, which you can use to find needed Event or to assign as foreign key.
Change you form
<%= form_with(model: [#event, #question], local: true) do |form| %>
<%= form.label :body %>
<%= form.rich_text_area :body %>
<%= form.submit 'Create Question' %>
<% end %>
and controller a bit to work with nested routes
def new
#event = Event.find(params[:event_id])
#question = event.questions.build
end
def create
#event = Event.find(params[:event_id])
#question = event.questions.build(question_params)
#question.user_id = current_user.id
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
Also, you should remove the line accepts_nested_attributes_for from Event and Question models, since you never use it
What's the best way to create a form for models with many-to-many relations?
In detail:
I have 3 models: User, Task, TaskAssignment:
User Model
class User < ActiveRecord::Base
has_many :task_assignments
has_many :tasks, through: :task_assignments
end
Task Model
class Task < ActiveRecord::Base
has_many :task_assignments
has_many :users, through: :task_assignments
end
TaskAssignment Model (Join Table)
I can't use has_and_belongs_to_many, because I need additional fields in the TaskAssignment Model.
class TaskAssignment < ActiveRecord::Base
belongs_to :task
belongs_to :user
end
By creating a new task, there should be the possibility to assign multiple users to a task, so I made this form view:
Task Edit Form View
<%= form_for(#task) do |f| %>
<div class="field">
<%= f.label :note %><br>
<%= f.text_field :note %>
</div>
<select name="task[users]" size="5" multiple>
<% #users.each do |user| %>
<option value="<%= user.id %>"><%= user.email %></option>
<% end %>
</select>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Now, I wonder how to go on in my controller:
Task Controller
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render action: 'show', status: :created, location: #task }
else
format.html { render action: 'new' }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
I think I have to do something like that:
#task = Task.new(task_params)
users = ???
#task.users << users
Is that the way n:m data should be saved or are there any other magic rails functions?
How do i get the data from the select-box? I tried to receive them by setting the name of the select-box to name="task[users]", but the variable task_params contains only the note-field
There's a helper called accepts_nested_attributes_for that allows a parent object to create and update its nested objects. In this case, you want Task to be able to create and update TaskAssignment
First, allow Task to accept attributes for its kids and to assign attributes to them.
class Task < ActiveRecord::Base
has_many :task_assignments
has_many :users, through: :task_assignments
accepts_nested_attributes_for :task_assignments
end
That should point you in the right direction. Creating the form will look something like explained in fields_for for one-to-many.
You don't need to save your object in any special way. Just remember to allow :user_ids => [] in your task_params.
Also, your life might be a bit easier with the form helper collection_select.
<%= f.collection_select :user_ids, #users, :id, :email, {}, { :multiple => true, :size => 5 } %>
Edit: Added the update action, and on what line the error occurs
Model:
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :match_teams
has_many :teams, :through => :match_teams
accepts_nested_attributes_for :match_teams, :allow_destroy => true
end
Controller:
def new
#match = Match.new
#match_teams = 2.times do
#match.match_teams.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #match }
end
end
def update
#match = Match.find(params[:id])
respond_to do |format|
if #match.update_attributes(params[:match])
format.html { redirect_to #match, notice: 'Match was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
Nested model:
class MatchTeam < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
Association:
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
View:
<%= form_for(#match) do |f| %>
<%= f.fields_for :match_teams, #match_teams do |builder| %>
<%= builder.collection_select :team_id, Team.all, :id, :name, :include_blank => true %>
<% end %>
<% unless #match.new_record? %>
<div class="field">
<%= f.label :winning_team_id %><br />
<%= f.collection_select :winning_team_id, #match.teams, :id, :representation %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Params:
Processing by MatchesController#update as HTML
Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>"QIJChzkYOPZ1hxbzTZS8H3AXc7i
BzkKv3Z5daRmlOsQ=", "match"=>{"match_teams_attributes"=>{"0"=>{"team_id"=>"1", "
id"=>""}, "1"=>{"team_id"=>"3", "id"=>""}}, "winning_team_id"=>"3"}, "commit"=>"
Update Match", "id"=>"2"}
Creating a new match with 2 teams work fine, the edit view also shows the correct values, but the update action gives me this error.
undefined method `to_sym' for nil:NilClass
app/controllers/matches_controller.rb:65:in `block in update'
line 65: if #match.update_attributes(params[:match])
I've figured it out. I read that a join table like MatchTeams doesn't need an ID. I'm guessing this is true when not doing any nested forms. I redid my migration removing the exclusion of the id column, and now everything works fine. Don't we all love this stupid errors? :)
Without seeing the offending to_sym in your code, just know that the thing it's attached to has not been defined properly. If this is a variable such as #var.to_sym, you most likely:
Haven't set #var at all
Set it but it's returning nil because there are no matches (e.g. #var = #project.companies.first but #project has no companies tied to it).
You are missing a relevant bit of data in your params. If your to_sym is relying on data submitted through the form, it won't work if the user leaves out the bit of data you're assuming. In this case, you should test first to see if the data was entered before running .to_sym on it.
I have a model "Issue" and a nested Model "Relationship"
In the issue.rb I have mentioned:
has_many :relationships, :dependent => :destroy
accepts_nested_attributes_for :relationships, :allow_destroy => true
In relationship.rb I have mentioned:
belongs_to :issue
Following Ryan Bates Railcast#196 I have the following in my issues_controller:
relationship = #issue.relationships.build
However, I am encountering an error "unknown attribute: relationship"
Am I doing something incorrectly here? I do see the Relationships Attributes being passed to the server in the log however, this error does not let the create to be successful.
My expertise with rails is beginners level so kindly excuse me if I am asking a question which maybe deemed trivial.
Thanks for the help.
EDIT: The relevant Controller code:
#relationship = #issue.relationships.build
##relationship = Relationship.new(params[:relationship])
if #relationship.issue_id = ''
#relationship.issue_id = #issueid
end
if #relationship.cause_id = ''
#relationship.cause_id = #issueid
end
#relationship.save
redirect_to(:back, :notice => 'New Relationship was created')
What I see on the trace:
ActiveRecord::UnknownAttributeError in IssuesController#create
unknown attribute: relationship
Among the Issue parameters, I see the Relationship params being passed as expected:
"relationship"=>{"issue_id"=>"100",
"cause_id"=>""}
ANOTHER UPDATE
Posting the form_for code:
- form_for Issue.new do |f|
.field
= f.text_field :description, :class=>"formfield", :id=>"frm_descr"
.field
= f.hidden_field :wiki_url, :class=>"formfield", :id=>"frm_wiki_url"
.field
= f.hidden_field :short_url, :class=>"formfield", :id=>"frm_img_url"
.field
= f.hidden_field :title, :class=>"formfield", :id=>"frm_title"
= f.fields_for :relationship do |builder|
= builder.text_field :issue_id, :class=>"form_field", :id=>"frm_rel_issue_id", :value=>#issue.id
= builder.text_field :cause_id, :class=>"form_field", :id=>"frm_rel_cause_id"
.actions
= f.submit 'Create', :class=>"save_button", :name=>"save_issue_rel_button", :id=>"val_collector"
Change this line
= f.fields_for :relationship do |builder|
to this:
= f.fields_for :relationships do |builder|
Your issue has_many relationships - plural. That will give you the correct relationships_attributes parameters.
Here is the working skeleton code:
I created a new project and tried the combination of the other answers, and finally made it to work.
Here is my solution, after that are the things to watch out for. I am using different models so bear with me:
My models are: discussion has_many posts.
Discussion has no attributes.
Posts has content:text and discussion_id:integer.
Working Code
(model) discussion.rb
has_many :posts
accepts_nested_attributes_for :posts
(model) post.rb
belongs_to :discussion
routes.rb
resources :discussions do
resources :posts
end
(discussion view) _form.html.erb
<%= form_for(#discussion) do |f| %>
<%= f.fields_for :posts, #post do |p| %>
<%= p.text_area :content %>
<% end %>
<%= f.submit %>
<% end %>
(controller) discussions_controller.rb
def new
#discussion = Discussion.new
#post = #discussion.posts.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #discussion }
end
end
def create
#discussion = Discussion.new(params[:discussion])
respond_to do |format|
if #discussion.save
format.html { redirect_to(#discussion, :notice => 'Discussion was successfully created.') }
format.xml { render :xml => #discussion, :status => :created, :location => #discussion }
else
format.html { render :action => "new" }
format.xml { render :xml => #discussion.errors, :status => :unprocessable_entity }
end
end
end
Possible things that can go wrong
First, Thilo was right, I get unknown attribute: post if I do
# WRONG!
f.fields_for :post
Second, I have to have the #post instance variable in new action otherwise the post.context textarea will not show up.
# REQUIRED!
#post = #discussion.posts.build
Third, If I use the f.fields_for #post, the create action will complain unknown attribute: post too.
# WRONG!
f.fields_for #post do |p|
Use this instead:
# RIGHT!
f.fields_for :posts, #post do |p|
The End
So yeah, I wish we get to see more documentations on this (can't see any useful ones). For example I see some use of form_for [#discussion, #post] but I can never get it to work.
By using accepts_nested_attributes, you have created a setter method relationship_attributes=.
There are a couple of things I noticed that need to change.
You don't need to set
#relationship = #issue.relationships.build
Your form should be the following (you have f.fields_for :relationship)
= form_for #issue do |f|
# your issue fields here
= f.fields_for :relationships do |r|
# your relationship fields here
The beauty here is that you won't have to set any ids or anything.
I assume you are constructing the relationship in your controller and then trying to use it in the view. In order for this to be visible, you must make it an instance variable. All you need to do is throw an # symbol in from of the name of relationship, as you have done with #issue.
#relationship = #issue.relationships.build
Edit: due to further information provided by the OP after the original question was asked, this answer is now clearly not applicable.