administrator.rb:
class Administrator < ActiveRecord::Base
has_one :administrator_role, dependent: :destroy
has_one :role, through: :administrator_role
end
role.rb:
class Role < ActiveRecord::Base
has_many :administrator_roles
has_many :administrators, through: :administrator_roles
end
administrator_role.rb:
class AdministratorRole < ActiveRecord::Base
belongs_to :administrator
belongs_to :role
end
in view for "new" action administrator_controller:
<%= form_for #administrator do |f| %>
<%= render 'shared/errors', object: #administrator %>
<div class="form-group">
<%= f.label :role_id, "Роль:" %>
<%= f.collection_select(:role_id, #roles, :id, :name) %>
</div>
...
<%= f.submit 'Save', class: 'btn btn-primary btn-lg' %>
<% end %>
administrator_controller.rb:
class AdministratorsController < ApplicationController
def new
#administrator = Administrator.new
#roles = Role.all
end
def create
#administrator = Administrator.new(administrators_params)
if #administrator.save
flash[:success] = "Account registered!"
redirect_to root_path
else
render :new
end
end
...
private
def administrators_params
params.require(:administrator).permit(:login, :password, :password_confirmation, :role_id)
end
end
when you open the page get the error:
undefined method `role_id' for #<Administrator:0x007f6ffc859b48>
Did you mean? role
How to fix it? if I put in place role_id a role, when you create administrator will get the error:
ActiveRecord::AssociationTypeMismatch (Role(#69964494936160) expected, got String(#12025960)):
You have to rewrite the form as below:
<%= form_for #administrator do |f| %>
<%= render 'shared/errors', object: #administrator %>
<div class="form-group">
<%= f.fields_for :role do |role_form| %
<%= role_form.label :role_id, "Роль:" %>
<%= role_form.select(:id, #roles.map { |role| [role.name, role.id] }) %>
<% end %>
</div>
...
<%= f.submit 'Save', class: 'btn btn-primary btn-lg' %>
<% end %>
You also need to add 1 line which enables the nested form logic as:
class Administrator < ActiveRecord::Base
has_one :administrator_role, dependent: :destroy
has_one :role, through: :administrator_role
accepts_nested_attributes_for :role
end
And also change the controller like:
class AdministratorsController < ApplicationController
#....
private
def administrators_params
params.require(:administrator).permit(
:login, :password,
:password_confirmation,
role_attributes: [ :id ]
)
end
end
When you are using has_one association, you get the below method, but not association_id=, and that is what error is saying.
association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
Related
I'm trying to create a form where a user can answer questions and submit to a results page, however, when I am trying to pass the AnsweredQuestions attributes through the Quiz controller it's telling me it's an 'unknown attribute'.
Error i'm receving: unknown attribute 'answered_questions_attributes' for AnsweredQuestion.
Quiz.rb:
class Quiz < ApplicationRecord
validates :title, presence: true, length: { maximum: 50 }
has_many :questions, dependent: :destroy
has_many :answered_questions, through: :questions, dependent: :destroy
accepts_nested_attributes_for :answered_questions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :questions, reject_if: :all_blank, allow_destroy: true
end
AnsweredQuestion.rb:
class AnsweredQuestion < ApplicationRecord
belongs_to :user
belongs_to :question
belongs_to :answer
belongs_to :quiz
end
Quiz controller:
def show
#quiz = Quiz.find(params[:id])
#questions = Question.all
#answered_questions = current_user.answered_questions.build
#quiz.answered_questions.build
end
def create
#quiz = Quiz.new(show_params)
if #quiz.save
flash[:success] = "You have created a new quiz!"
redirect_to #quiz
else
render 'new'
end
end
def post_answered_questions
#answered_question = current_user.answered_questions.build(show_params)
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
private
def user_completed_quiz
if(current_user.answered_questions.pluck(:quiz_id).uniq.include?(params[:id].to_i))
redirect_to quizzes_path
end
end
def show_params
params.require(:quiz).permit(:title, answered_questions_attributes: [:id, :answer_id, :question_id, :user_id, :quiz_id], questions_attributes: [:id, :question_title, :quiz_id, :done, :_destroy, answers_attributes: [:id, :answer_title, :question_id, :quiz_id, :correct_answer, :_destroy]])
end
end
show.html.erb (in quizzes):
<%= form_for(#quiz, url: post_answered_questions_quizzes_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
UPDATED:
show.html.erb (quizzes):
<%= form_for(#answered_questions, url: answered_questions_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
AnsweredQuestion controller:
class AnsweredQuestionsController < ApplicationController
def show
#answered_question = AnsweredQuestion.new
end
def create
#answered_question = current_user.answered_questions.build(answered_params)
binding.pry
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
def edit
#answered_questions = AnsweredQuestion.find(params[:id])
end
def destroy
AnsweredQuestion.find(params[:id]).destroy
flash[:success] = "Answered quiz deleted"
redirect_to answered_questions_url
end
private
def answered_params
params.require(:answered_questions).permit(:question_id, :answer_ids, :user_id, :quiz_id, :id, :_destroy)
end
end
It looks like you are accepting nested attributes for Quiz but using the nested attributes in AnsweredQuestion instead of in Quiz. You need to add accepts_nested_attributes_for in the other model and in show_params it should be quiz_attributes instead of answered_questions_attributes. Although you are using show_params twice, once over one model and the second one over the other, so you may need two params. Otherwise you'll break the other one.
I have
Models
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
end
class GroupArtist < ApplicationRecord
belongs_to :group
belongs_to :artist
end
class Artist < ApplicationRecord
has_many :group_artists
has_many :groups, through: :group_artists
end
group_artists table has these columns
class CreateGroupArtists < ActiveRecord::Migration[5.1]
def change
create_table :group_artists, id: false do |t|
t.references :group, foreign_key: true, null: false
t.references :artist, foreign_key: true, null: false
t.string :role
t.string :acting
t.timestamps
end
end
end
Controller
class GroupsController < ApplicationController
def new
#group = Group.new
#singers = #group.singers.build
#guitarists = #group.guitarists.build
#artists = Artist.all // for a selection
end
def create
#group = Group.new(allowed_params)
#group.save
end
private
def allowed_params
params.require(:group).permit(:name, :singers, :guitarists, group_artists_attributes: [:group_id, :artist_id, :role, :acting])
end
end
views/groups/_form.html.erb
<%= form_for #group do |f| %>
<%= f.label "Singers" %>
<%= f.fields_for :singers do |singer| %>
<%= singer.select(:artist_id, #artists.collect { |a| [a.name, a.id.to_i] }, { include_blank: true }) %>
<% end %>
<%= f.label "Guitarists" %>
<%= f.fields_for :guitarists do |guitarist| %>
<%= guitarist.select(:artist_id, #artists.collect { |a| [a.name, a.id.to_i] }, { include_blank: true }) %>
<% end %>
<%= f.submit "Submit" %>
<% end %>
It creates the group all right, but doesn't create the relation in GroupArtist.
I know something's missing in the controller part. I should add something after the ".build" like (role: "Singer") but it doesn't do anything as well.
Ruby -v 2.4.1
Rails -v 5.1.3
Since you are using group_artists as more than just a simple join table you need to use nested attributes to create a row with metadata:
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
accepts_nested_attributes_for :group_artists,
reject_if: ->{|a| a[:artist_id].blank? || a[:role].blank?}
end
Also the structure using different associations to create nested records based on the role of the band members is not really scalable - for every possible role the class / form will swell.
Instead you may want to use two selects:
<%= form_for #group do |f| %>
<fields_for :group_artists do |ga| %>
<div class="field">
<%= f.label :artist_id, "Artist" %>
<%= f.collection_select :artist_id, Artist.all, :id, :name %>
<%= f.label :role %>
<%= f.select :role, %w[ Singer Guitarist ] %>
</div>
<% end %>
<%= f.submit "Submit" %>
<% end %>
Also you are not saving the record in your #create method.
def create
#group = Group.new(allowed_params)
if #group.save
# ...
else
render :new
end
end
You should add this in your controller action create
def create
#group = Group.new(allowed_params)
#group.save
end
Group.new doesn't persist model in DB but save after yes.
These were the adds and changes I needed to make it work.
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
accepts_nested_attributes_for :group_artists,
reject_if: proc { |a| a[:artist_id].blank? || a[:role].blank? },
allow_destroy: true
end
class GroupsController < ApplicationController
def new
#group = Group.new
#group.group_artists.build
#artists = Artist.all // for a selection
end
def create
#group = Group.new(allowed_params)
#group.save
end
end
groups/_form.html.erb
<%= form_for #group do |f| %>
<div class="singers">
<%= f.label :singers %>
<%= f.fields_for :group_artists do |ga| %>
<%= ga.collection_select :artist_id, Artist.all, :id, :name %>
<%= ga.hidden_field :role, value: 'Singer' %>
<% end %>
</div>
<div class="guitarists">
<%= f.label :guitarists %>
<%= f.fields_for :group_artists do |ga| %>
<%= ga.collection_select :artist_id, Artist.all, :id, :name %>
<%= ga.hidden_field :role, value: 'Guitarist' %>
<% end %>
</div>
<%= f.submit "Submit" %>
<% end %>
That way you don't have to specify the artist's role if his name's selected in the singers or guitarists labeled div.
That's it. Thanks to max, for guiding me to the right direction.
I'm trying to create a form where users can see a list of their friends and add or remove friends from lists (sort of like facebook's groups). For the create list view, I can get the form working properly, but for the edit view I'm having trouble with check_box syntax and fields_for.
routes.rb
resources :friendships
resources :friend_lists
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, through: :friendships
has_many :friend_lists
has_many :flist_memberships, class_name: 'FlistMembership', foreign_key: 'member_id', dependent: :destroy
...
end
class FriendList < ActiveRecord::Base
belongs_to :user
has_many :flist_memberships
has_many :members, through: :flist_memberships
accepts_nested_attributes_for :members, allow_destroy: true
accepts_nested_attributes_for :flist_memberships, allow_destroy: true
end
class FlistMembership < ActiveRecord::Base
belongs_to :friend_list
belongs_to :member, class_name: 'User'
end
class FriendListsController < ApplicationController
def new
#friend_list = current_user.friend_lists.build
#friends = current_user.friends.paginate(page: params[:page])
#friend_list.flist_memberships.build
end
def create
#friend_list = current_user.friend_lists.build(friend_list_params)
respond_to do |format|
format.html {
if #friend_list.save
flash[:success] = "Friends list created!"
redirect_to friendships_path
else
render 'friend_lists/new'
end
}
end
end
def edit
#friend_list = FriendList.find(params[:id])
#friends = current_user.friends
end
def update
#friend_list = FriendList.find(params[:id])
if #friend_list.update_attributes(friend_list_params)
flash[:success] = "Friend list updated!"
redirect_to friend_list_path(#friend_list)
else
render 'edit'
end
end
private
def friend_list_params
params.require(:friend_list).permit(:name, flist_memberships_attributes: [:id, :member_id, :friend_list_id])
end
end
new.html.erb
<%= form_for #friend_list do |f| %>
<%= f.label :name, "Name for this friends list:" %>
<%= f.text_field :name %>
<% #friends.each do |friend| %>
<%= f.fields_for :flist_memberships do |m| %>
<%= m.check_box :member_id, {}, friend.id %>
<%= m.label :member_id, friend.name %>
<% end %>
<% end %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
edit.html.erb
<%= form_for #friend_list do |f| %>
<%= f.label :name, "Name for this friends list:" %>
<%= f.text_field :name %>
<%= f.fields_for :flist_memberships do |m| %>
<%= m.collection_check_boxes(:member_id, #friends.all, :id, :name) %>
<% end %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
new.html.erb renders and saves the selected items correctly, but I can't figure out how to write the edit form so that it doesn't iterate through the collection once for each item in the collection (both with collection_check_boxes and the form as written in new.html.erb). Trying
<%= f.fields_for :flist_memberships do |m| %>
<%= m.check_box :id %>
<%= m.label :id, :name %>
<% end %>
in the edit form just passes the string "id" as params rather than pulling the id of each member in the collection, and I'm not sure how to pull the individual item's name/id without iterating over the collection, which goes back to the multiple renders problem. I'm using cocoon elsewhere in the project, but for this I'd rather present the user a list of all options and allow them to check a box for each one, rather than having to manually add each item on the list. If there is a way to do it with cocoon, I'd be happy to hear it.
I cannot seem to get nested attributes to save to the database, though I can see the params in terminal. I am using Rails 4.2.
Here are my models:
class Device < ActiveRecord::Base
belongs_to :hub
has_many :accessories, dependent: :destroy
accepts_nested_attributes_for :accessories,
reject_if: proc { |attributes| attributes['material'].blank? },
allow_destroy: true
end
class Accessory < ActiveRecord::Base
belongs_to :device
end
Here is the controller. I have my device model nested under user and hub model.
class DevicesController < ApplicationController
def edit
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
end
def update
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
if #device.update_attributes(device_params)
flash[:success] = "update successfully"
redirect_to user_hub_device_path(#user, #hub, #device)
else
render 'edit'
end
end
private
def device_params
params.require(:device).permit(:model, :hub_id, :resolution, :materials, :startcost, :take_online, :delivery_time, :unitcost, :color, :accessories, :accessories_attributes => [:id, :name, :cost, :color, :device_id, :_destroy])
end
end
Finally is my form.
<%= form_for([#user, #hub, #device]) do |f| %>
<fieldset>
<div id="material">
<%= f.fields_for :accessories do |a| %>
<%= render 'devices/accessory', a: a %>
<% end %>
</div>
</fieldset>
The partial:
<div class="row">
<%= a.collection_select :name, Material.all, :material, :material %>
<%= a.text_field :cost, id: "right-label" %>
<%= a.text_field :color, id: "right-label" %>
<%= a.check_box :_destroy %>
</div>
You are whitelisting params[:device][:materials] but you are checking attributes['material'].blank? (note the the s on the end). Which causes the nested attributes to be rejected.
I have few models - User, Teacher and TeacherLeader.
class User < ActiveRecord::Base
attr_accessible ...,
:teacher_attributes
has_one :teacher
has_one :teacher_leader
accepts_nested_attributes_for :teacher_leader
end
class Teacher < ActiveRecord::Base
belongs_to :user
has_one :teacher_leader
end
class TeacherLeader < ActiveRecord::Base
belongs_to :user
belongs_to :teacher
end
I would like to fill TeacherLeader via nested attributes. So, i do such things in controller:
class TeacherLeadersController < ApplicationController
...
def new
#user = User.new
#teacher_leader = #user.build_teacher_leader
#teachers_collection = Teacher.all.collect do |t|
[ "#{t.teacher_last_name} #{t.teacher_first_name} #{t.teacher_middle_name}", t.id ]
end
#choosen_teacher = #teachers_collection.first.last unless #teachers_collection.empty?
end
end
And also have such view (new.html.erb):
<%= form_for #user, :url => teacher_leaders_url, :html => {:class => "form-horizontal"} do |f| %>
<%= field_set_tag do %>
<% f.fields_for :teacher_leader do |tl| %>
<div class="control-group">
<%= tl.label :teacher_id, "Teacher names", :class => "control-label" %>
<div class="controls">
<%= select_tag( :teacher_id,
options_for_select( #teachers_collection, #choosen_teacher )) %>
</div>
</div>
<% end %>
<% end %>
...
<%= f.submit "Create", :class => "btn btn-large btn-success" %>
<% end %>
Problem is that select form here does NOT appear. Why? Do i do something wrong?
<%= f.fields_for :teacher_leader do |tl| %>