Rails 3.2 - accepts_nested_attributes_for and join models - ruby-on-rails-3

I have the following models: user, role, user_role (user_role is a join model)
I am trying to edit a user's roles using checkboxes on the user#edit page. Here's my attempt, I feel like I'm missing something significant, or taking the wrong approach.
user.rb
has_many :user_roles, dependent: :destroy
has_many :roles, through: :user_roles
attr_accessible :user_roles_attributes
accepts_nested_attributes_for :user_roles, reject_if: lambda { |a| a[:role_id] == 0 }, allow_destroy: true
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym.downcase }
end
def setup_roles!
Role.all.each { |role|
user_roles.build(user_id: id, role_id: role.id) unless has_role?(role.name.to_sym)
}
end
user_role.rb
belongs_to :user
belongs_to :role
delegate :name, to: :role
role.rb
has_many :user_roles
has_many :users, through: :user_role
users_controller.rb
def edit
#user = User.find(params[:id])
#user.setup_roles!
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = 'User was successfully updated.'
redirect_to edit_user_path(#user)
else
render :edit
end
end
users/edit.html.haml
= form_for #user do |f|
= f.fields_for(:user_roles) do |role_form|
= role_form.check_box :role_id, {}, role_form.object.role_id, 0
= role_form.hidden_field :user_id
= role_form.label :name, role_form.object.name
= f.submit 'Update'

Here is my solution. I received a lot of help from This Post at RubySource. The way the checkbox is setup, it will destroy a UserRole if "unchecked", and only create it when it is "checked" (why the '0', '1' is on that line.)
users_controller.rb
def edit
#user = User.find(params[:id])
#user.setup_roles!
end
user.rb
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym.downcase }
end
def setup_roles!
Role.all.each { |role|
user_roles.build(role: role) unless has_role?(role.name.to_sym)
}
end
users/edit.html.haml
= form_for #user do |f|
= f.fields_for :user_roles do |builder|
= builder.check_box :_destroy, { checked: builder.object.persisted? }, '0', '1'
= builder.label :_destroy, builder.object.role.name
= builder.hidden_field :role_id
= f.submit 'Update'

Related

Rails 3.2 fields_for not showing on has_one and accepts_nested_attributes_for

I use gem 'nested_form' in rails 3.2
Models:
meeting_agenda.rb:
class MeetingAgenda < ActiveRecord::Base
has_many :meeting_questions, :inverse_of => :meeting_agenda
accepts_nested_attributes_for :meeting_questions, allow_destroy: true
attr_accessible :meeting_questions_attributes
end
meeting_question.rb:
class MeetingQuestion < ActiveRecord::Base
has_one :meeting_answer, :inverse_of => :meeting_question
accepts_nested_attributes_for :meeting_answer
attr_accessible :meeting_answer_attributes
end
meeting_answer.rb
class MeetingAnswer < ActiveRecord::Base
belongs_to :meeting_question
end
Controller:
class MeetingProtocolsController < ApplicationController
def new
#agenda = MeetingAgenda.new
rescue => error
render_403
end
def create
#agenda = MeetingAgenda.new(params[:meeting_agenda])
if #agenda.save && protocol.save
flash[:notice] = l(:notice_successful_create)
redirect_to action: 'show', id: #agenda.id
else
render action: 'new'
end
end
View:
new.html.haml:
= nested_form_for #agenda do |f|
# ...
= f.fields_for :meeting_questions do |question|
# ...
# Fields for answer is not showing!
= question.fields_for :meeting_answer do |answer| # <-- blank
%p # <-- blank
= answer.label :reporter_id_is_contact, t(:label_meeting_question_user_is_contact) # <-- blank
= answer.check_box :reporter_id_is_contact; # <-- blank
= question.link_to_remove l(:button_delete) # <-- blank
= f.link_to_add l(:button_add), :meeting_questions
= submit_tag l(:button_create)
has_one relation with accepts_nested_attributes_for is empty.
Same form working great on rails 5.

Couldn't find <Model> with ID=14 for Property with ID=1

when update, i got error like this.
Couldn't find PropertyAcceptanceCriterion with ID=14 for Property with ID=1
this error occur when off the checkbox, and update(save).
what should i do next,
model definition is
class PropertyAcceptance < ActiveRecord::Base
belongs_to :property
belongs_to :property_acceptance_criterion
end
class PropertyAcceptanceCriterion < ActiveRecord::Base
attr_accessible :name
has_many :property_acceptances, dependent: :destroy
has_many :properties, through: :property_acceptances
end
class Property < ActiveRecord::Base
attr_accessible :rent
attr_accessible :room_no
attr_accessible :property_acceptance_criterions_attributes
attr_accessible :property_acceptance_criterion_ids
has_many :property_acceptances, dependent: :destroy
has_many :property_acceptance_criterions, through: :property_acceptances
accepts_nested_attributes_for :property_acceptance_criterions, reject_if: lambda { |a| a[:name].blank? }
end
view definition is
= simple_nested_form_for #property do |f|
= f.input :room_no, input_html: {class: 'span2'}
= f.input :rent, input_html: {class: 'span2'}
= f.association :property_acceptance_criterions, as: :check_boxes
= f.simple_fields_for :property_acceptance_criterions do |c|
= c.input :name, label: "add for #{t('activerecord.attributes.property.property_acceptance_criterions')}" if c.object.new_record?
controller definition is
class Insurance::PropertiesController < Insurance::InsuranceController
before_filter :load_property, only: [:edit, :update]
before_filter :new_property, only: [:new, :create]
def new
#property.property_acceptance_criterions.build
end
def create
#property.attributes = params[:property]
if #property.save
redirect_to #property, success: t('activerecord.flash.property.actions.create.success')
else
render :new
end
end
def edit
#property.property_acceptance_criterions.build
end
def update
if #property.update_attributes(params[:property]) # ← error occur
redirect_to #property, success: t('activerecord.flash.property.actions.update.success')
else
render :edit
end
end
private
def load_property
#property = Property.find(params[:id])
end
def new_property
#property = Property.new
end
end
error is
Couldn't find PropertyAcceptanceCriterion with ID=14 for Property with ID=1
params is
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"+Zx7l7mAbX12PSO873x5NDxNOIeEe6bEDEdVnys+a98=",
"property"=>{
"room_no"=>"000",
"rent"=>"80000",
"property_acceptance_criterion_ids"=>["13", "25", ""],
"property_acceptance_criterions_attributes"=>{
"0"=>{"id"=>"13"}, "1"=>{"id"=>"14"}, "2"=>{"id"=>"25"}, "3"=>{"name"=>""}
},
"commit"=>"update",
"id"=>"1"}

Nested Form: Can’t populate join table between parent and child if child exists / Couldn't find Child with ID=1 for ParentChildJoin with ID=

I cleaned up my code, it looks much nicer now, but still doesn’t work. It starts to be a pain…
I just can’t save a parent with an existing child in nested form with parent has_many childs :through joinmodel.
In my case a Project has_many contributing Teachers and many contributing Pupils, both are Join-Models to Users. A Project has_many Schools as well.
(May be I should better name the models Teacherize and Pupilize or ProjectTeacher and ProjectPupil.)
As long as all records are new it all works fine. As soon as I want to connect an existing User as new Teacher of a new Project I get the following error:
Couldn't find User with ID=1 for Teacher with ID=
(1 is the correct user ID)
The problem should be somewhere her in my helper to setup empty form fields:
At least I guess so...
module ProjectsHelper
def setup_project(project)
if project.teachers.length <= 0 # usually there is just one teacher, so add one if there isn't one
teacher = project.teachers.build(:role_in_project => 'master')
if user_signed_in?
#teacher = project.teachers.new(:role_in_project => 'master', :user => current_user)
teacher.user = current_user # associate first teacher with current_user
else
#teacher = project.teachers.build
teacher.user = User.new # associate first teacher with a new user instance
end
end
if project.project_schools.length <= 0 # usually there is just one school, so add one if there isn't one
project_school = project.project_schools.build
project_school.school = School.new
end
if project.pupils.length < 3 # There can be up to 3 people, so add a blank fieldset as long as there are less than 3
pupil = project.pupils.build
pupil.user = User.new
end
project
end
end
These are my params received:
{"utf8"=>"✓", "authenticity_token"=>"uCCMk/s3SpDfR7+fXcsCOHPvfvivBQv8pVFVhdh6iro=",
"project"=>{
"teachers_attributes"=>{
"0"=>{
"id"=>"",
"user_attributes"=>{
"id"=>"1",
"gender"=>"male",
"title"=>"",
"firstname"=>"Firstname1",
"name"=>"Lastname1",
"faculty"=>"",
"fon"=>"",
"fax"=>""}
}
},
"id"=>"",
"title"=>"First Project",
"description"=>"This is a foo bar project!",
"presentation_type"=>"experimentell",
"note"=>""
},
"commit"=>"Register Project",
"action"=>"create",
"controller"=>"projects"
}
The case isn’t too abstract; it has to be possible to achieve it.
It’s just to connect a new parent record with an existing child record!
In this article, which is very good, exactly the case is explaint:
http://rubysource.com/complex-rails-forms-with-nested-attributes
# app/helpers/form_helper
module FormHelper
def setup_user(user)
user.address ||= Address.new
(Interest.all - user.interests).each do |interest|
user.interest_users.build(:interest => interest)
end
user.interest_users.sort_by! {|x| x.interest.name }
user/tmp/clean-controllers.md.html
end
end
There the interest is existing and gets connected through a new record in interest_uesers.
Why do I get the error when trying to do the same thing?
project.teachers.build(:user => current_user)
I studied several articles and casts, but none of them connect existing childs.
http://railscasts.com/episodes/196-nested-model-form-revised
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Rails 3.1+ Nested Forms Issue: Can't mass-assign protected attributes
Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated
Quote: “accepts_nested_fields_for is used to create and modify related objects in a form. It can be used to populate join table, which is kind of what you're trying to do. However, using accepts_nested_fields_for to populate the join table is impossible with a HABTM relationship.”
That’s what I wanna do! Populate the join table!
It starts to be frustrating and I’d be glad to get some help!
My Models
class Project < ActiveRecord::Base
attr_accessible :title, :description, :presentation_type, :note,
:project_schools_attributes, :schools_attributes, :teachers_attributes, :pupils_attributes,
:users_attributes
validates_presence_of :title
validates_presence_of :description
validates_presence_of :presentation_type
has_many :project_schools, :dependent => :destroy
accepts_nested_attributes_for :project_schools
has_many :schools, :through => :project_schools
#accepts_nested_attributes_for :schools, :reject_if => :all_blank
has_many :pupils, :dependent => :destroy
accepts_nested_attributes_for :pupils, :reject_if => :all_blank
has_many :users, :through => :pupils
has_many :teachers, :dependent => :destroy
accepts_nested_attributes_for :teachers, :reject_if => :all_blank
has_many :users, :through => :teachers
#accepts_nested_attributes_for :users, :reject_if => :all_blank
end
class ProjectSchool < ActiveRecord::Base
attr_accessible :role_in_project, :comment,
:school_attributes, :school_id, :project_id
belongs_to :school
accepts_nested_attributes_for :school
belongs_to :project
end
class School < ActiveRecord::Base
attr_accessible :email, :fax, :fon, :name, :place, :street, :type_of_school, :www, :zip
has_many :project_schools
has_many :projects, :through => :project_schools
has_many :users # in real live they are named teachers and pupils but in this case the association goes directly to a user_id, not to teacher/pupil model
validates_presence_of :name, :type_of_school, :street, :place, :zip, :fon
validates :email, :format => { :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }, :allow_blank => true
end
class Teacher < ActiveRecord::Base
attr_accessible :role_in_project, :user, :user_attributes, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
serialize :role_in_project
end
class Pupil < ActiveRecord::Base
attr_accessible :classname, :user_attributes #, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
serialize :roles
belongs_to :school
has_many :teachers
has_many :pupils
has_many :projects, :through => :teachers
has_many :projects, :through => :pupils
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :gender, :firstname, :name, :street, :place, :title, :faculty, :assignment,
:classname, :zip, :fon, :fax, :school_id, :roles,
:password, :added_by_user_id, :password_confirmation, :remember_me
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable#, :validatable
after_initialize :init
def init
# the default guest user
self.roles ||= ['default'] #will set the default value only if it's nil
end
end
My controller
class ProjectsController < ApplicationController
require 'axlsx'
before_filter :load_page, only: [:show, :index, :destroy]
def new
#project = Project.new
end
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
sign_in(:user, #project.teachers[0].user) unless user_signed_in?
# TODO: send mail
# save as excel file in dropbox
save_in_dropbox(#project)
format.html { redirect_to #project, notice: t('project.was_created') }
else
logger.debug #project.errors.inspect
format.html { render action: "new" }
end
end
end
end
projects/_form.html.haml
%h1
= t('project.register_headline')
= simple_form_for( setup_project(#project), :html => {:class => 'form-horizontal'} )do |f|
= f.error_notification
#teachers-wrapper.well
%span.jumpanchor#lehrkraft_anchor
%fieldset.form-inputs
= f.simple_fields_for :teachers do |teacher|
= render "teacher_fields", :f => teacher
.school-wrapper.well
%span.jumpanchor#schule_anchor
%h2
Informationen zur Schule
%fieldset.form-inputs
= f.simple_fields_for :project_schools do |project_school|
= render "school_fields", :f => project_school
.project-wrapper.well
%span.jumpanchor#projekt_anchor
%h2
Informationen zum Projekt der Schüler
%fieldset.form-inputs
= f.hidden_field :id
= f.input :title, :input_html => { :class => 'span6' }
= f.input :description, :input_html => { :class => 'span6', rows: 5 }
= f.input :presentation_type, collection: ['theoretisch', 'experimentell'], as: :radio_buttons, :class => 'controls-row', :input_html => { :class => 'inline' }
.clearfix
= f.input :note, :input_html => { :class => 'span6', rows: 3 }
.pupils-wrapper.well
%span.jumpanchor#schuler_anchor
%fieldset.form-inputs
= f.simple_fields_for :pupils do |pupil|
= render "pupil_fields", :f => pupil
projects/_teacher_fields.html.haml
= f.simple_fields_for :user do |user|
=# render "teacher_user_fields", :f => user
%h2
Betreuende Lehrkraft
- if user_signed_in?
= user.input :email, :disabled => true, :input_html => {:class => 'email_validation'}
- else
= user.input :email, :autofocus => true, :input_html => {:class => 'email_validation'}, :hint => 'Dies muß Ihre eigene E-Mailadresse sein!'
.details
=# user.hidden_field :id
= user.input :id
= user.input :gender, collection: [:female, :male]
= user.input :title
= user.input :firstname
= user.input :name
= user.input :faculty
= user.input :fon
= user.input :fax
It is Rails 3.2 with Ruby 1.9.3

How do I correct this model association?

I've created an album model and a photo model and added it to an existing rails application. I've made the photos model belong to the album model and the album model belong to an existing profile model that belongs to a user model. I don't know if I've associated them wrong and why I'm getting an error.
It's worth noting that when I go to URL/albums then everything works as it should but when I go to URL/profiles/1 (the code below is pasted in the show.html.erb file in the views/profile/ folder) then I get the error below.
This is a simple problem that I just can't solve. The four model files are below:
Album.rb:
class Album < ActiveRecord::Base
belongs_to :profile
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos, :allow_destroy => true
end
Profile.rb:
class Profile < ActiveRecord::Base
belongs_to :user
has_many :albums
def self.get_location(profile)
location = []
location << profile.city unless profile.city.blank?
location << profile.state unless profile.state.blank?
location << profile.country unless profile.country.blank?
location << profile.postal_code unless profile.postal_code.blank?
location
end
def self.missing_fields(profile)
missing = []
if profile.first_name.blank?
missing << "first name"
end
if profile.last_name.blank?
missing << "last name"
end
if profile.job_title.blank?
missing << "job title"
end
missing
end
end
Photo.rb:
require 'paperclip'
class Photo < ActiveRecord::Base
belongs_to :album
has_attached_file :upload,
:url => "/images/:id/:style/:basename.:extension",
:path => ":rails_root/public/images/:id/:style/:basename.:extension",
:styles => {
:thumb => "75x75>",
:small => "200x200>"
}
#add in any validations you may want
end
User.rb:
class User < ActiveRecord::Base
include Gravtastic
gravtastic :size => 120
# associations
has_many :albums
has_many :photos, :through => :albums
has_many :authorizations, :dependent => :destroy
has_one :profile, :dependent => :destroy
has_many :resumes, :dependent => :destroy, :order => 'created_at DESC'
has_many :thoughts, :dependent => :destroy, :order => 'created_at DESC'
has_many :user_threads, :dependent => :destroy, :order => 'created_at ASC'
accepts_nested_attributes_for :profile
# virtual attributes
attr_accessor :first_name, :last_name
# validations
validates_presence_of :first_name
validates_presence_of :last_name
validates_length_of :username, :minimum => 4, :message => " is too short"
validates :email, :email => {:message => " is not valid"}
validates_uniqueness_of :email, :case_sensitive => false
validates_uniqueness_of :username, :case_sensitive => false
validates_length_of :password, :minimum => 4, :message => " is too short"
# authlogic
acts_as_authentic do |config|
config.crypto_provider = Authlogic::CryptoProviders::MD5
config.maintain_sessions = false
config.validate_email_field = false
config.validate_login_field = false
config.validate_password_field = false
config.login_field = :email
config.validate_login_field = false
end
def self.create_from_hash!(hash)
user = User.new(:username => Time.now.to_i, :email => '', :auth_provider => hash['provider'])
user.save(:validate => false)
if hash['provider'].downcase == 'twitter'
user.profile = Profile.create(:first_name => Twitter::Client.new.user(hash['user_info'] ['nickname'].to_s).name)
else
user.profile = Profile.create(:first_name => hash['user_info']['first_name'], :last_name => hash['user_info']['last_name'])
end
user
end
def deliver_password_reset_instructions!
reset_perishable_token!
UserMailer.deliver_password_reset_instructions(self)
end
def activate!
self.active = true
save(false)
end
def deliver_activation_instructions!
reset_perishable_token!
UserMailer.deliver_activation_instructions(self)
end
end
The profile controller has this snippet:
def show
#user = User.find_by_username(params[:id])
#profile = #user.profile
#location = Profile.get_location(#profile)
#resumes = #user.resumes
#albums = #user.albums
#photos = #user.photos
#thoughts = #user.thoughts
#shouts = UserThread.find_profile_shouts(#profile)
#shouters = UserThread.find_shouters(#shouts)
#user_thread = UserThread.new
end
The view has this:
<div id="profile_right_col">
<h2>Albums</h2>
<p>
<b>Name:</b>
<%= #albums %><br />
<% #albums.photos.each do |photo| %>
<h3><%= photo.title %></h3>
<%= image_tag photos.upload.url(:small) %>
<% end %>
</p>
<%= link_to 'Edit', edit_album_path(#albums) %> |
<%= link_to 'Back', albums_path %>
</div>
The Action Controller exception shows:
ActiveRecord::StatementInvalid in Profiles#show
Showing /Users/pawel/Ruby/Apps/cvf/app/views/profiles/show.html.erb where line #137 raised:
SQLite3::SQLException: no such column: albums.user_id: SELECT "albums".* FROM "albums" WHERE ("albums".user_id = 4)
Extracted source (around line #137):
<h2>Albums</h2>
<p>
<b>Name:</b>
<%= #albums %><br />
<% #albums.photos.each do |photo| %>
<h3><%= photo.title %></h3>
You dont have #album variable defined in your show action (you have #albums and the in the views you need to go through #albums array). So its value is nil and it doesn`t have method photos.
It worked after I added Paperclip::Railtie.insert to my application.rb.

How do I create a join action between a group and a user?

user.rb
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
membership.rb
class Membership < ActiveRecord::Base
attr_accessible :user_id, :group_id
belongs_to :user
belongs_to :group
validates_uniqueness_of :user_id, :message => "You can only join one group!"
end
group.rb
has_many :memberships, :dependent => :destroy
has_many :users, :through => :memberships
groups_controller.rb
def join
#group = Group.find(params[:id])
#m = #group.memberships.build(:user_id => current_user.id)
respond_to do |format|
if #m.save
format.html { redirect_to(#group, :notice => 'You have joined this group.') }
format.xml { head :ok }
else
format.html { redirect_to(#group, :notice => 'Join error.') }
format.xml { render :xml => #group.errors, :status => :unprocessable_entity }
end
end
end
memberships_controller.rb
class MembershipsController < ApplicationController
before_filter :authenticate_user!
def create
#membership = current_user.memberships.build(:group_id => params[:group_id])
if #membership.save
flash[:notice] = "You have joined this group."
redirect_to :back
else
flash[:error] = "Unable to join."
redirect_to :back
end
end
def destroy
#membership = current_user.memberships.find(params[:id])
#membership.destroy
flash[:notice] = "Removed membership."
redirect_to :back
end
end
Users have groups through a membership model which is a join table. My question. How do I create a join action for users to click on so that they can join a group?
You've already completed step 1, which is adding the controller actions. Now you just need to add the appropriate route and wire it up in your views.
In your routes.rb:
resources :groups do
get 'join', :on => :member
end
In your view file:
<%= link_to "Join this group", join_group_path(#group) %>
That's it!