Rails 3, nested multi-level forms and has_many through - ruby-on-rails

I'm trying to get it to work but it dosen't!
I have
class User < ActiveRecord::Base
has_many :events, :through => :event_users
has_many :event_users
accepts_nested_attributes_for :event_users
end
class Event < ActiveRecord::Base
has_many :event_users
has_many :users, :through => :event_users
accepts_nested_attributes_for :users
end
class EventUser < ActiveRecord::Base
set_table_name :events_users
belongs_to :event
belongs_to :user
accepts_nested_attributes_for :events
accepts_nested_attributes_for :users
end
And also the table-layout
event_users
user_id
event_id
user_type
events
id
name
users
id
name
And this is my form
<%= semantic_form_for #event do |f| %>
<%= f.semantic_fields_for :users, f.object.users do |f1| %>
<%= f1.text_field :name, "Name" %>
<%= f1.semantic_fields_for :event_users do |f2| %>
<%= f2.hidden_field :user_type, :value => 'participating' %>
<% end %>
<% end %>
<%= link_to_add_association 'add task', f, :users %>
<% end %>
The problem is that if I create a new user this way, it doesn't set the value of user_type (but it creates a user and a event_users with user_id and event_id). If I go back to the edit-form after the creation of a user and submit, then the value of user_type is set in events_users. (I have also tried without formtastic)
Any suggestions? Thanks!
----edit----
I have also tried to have the event_users before users
<%= semantic_form_for #event do |f| %>
<%= f.semantic_fields_for :event_users do |f1| %>
<%= f1.hidden_field :user_type, :value => 'participating' %>
<%= f1.semantic_fields_for :users do |f2| %>
<%= f2.text_field :name, "Name" %>
<% end %>
<% end %>
<%= link_to_add_association 'add task', f, :event_users %>
<% end %>
but then it only throws me an error:
User(#2366531740) expected, got
ActiveSupport::HashWithIndifferentAccess(#2164210940)
--edit--
the link_to_association is a formtastic-cocoon method (https://github.com/nathanvda/formtastic-cocoon) but I have tried to do other approaches but with the same result
---edit----
def create
#event = Event.new(params[:event])
respond_to do |format|
if #event.save
format.html { redirect_to(#event, :notice => 'Event was successfully created.') }
format.xml { render :xml => #event, :status => :created, :location => #event }
else
format.html { render :action => "new" }
format.xml { render :xml => #event.errors, :status => :unprocessable_entity }
end
end
end

To be honest, i have never tried to edit or create a has_many :through in that way.
It took a little while, and had to fix the js inside formtastic_cocoon to get it working, so here is a working solution.
You need to specift the EventUser model, and then fill the User model (the other way round will never work).
So inside the models you write:
class Event < ActiveRecord::Base
has_many :event_users
has_many :users, :through => :event_users
accepts_nested_attributes_for :users, :reject_if => proc {|attributes| attributes[:name].blank? }, :allow_destroy => true
accepts_nested_attributes_for :event_users, :reject_if => proc {|attributes| attributes[:user_attributes][:name].blank? }, :allow_destroy => true
end
class EventUser < ActiveRecord::Base
belongs_to :user
belongs_to :event
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
has_many :events, :through => :event_users
has_many :event_users
end
Then the views. Start with the events/_form.html.haml
= semantic_form_for #event do |f|
- f.inputs do
= f.input :name
%h3 Users (with user-type)
#users_with_usertype
= f.semantic_fields_for :event_users do |event_user|
= render 'event_user_fields', :f => event_user
.links
= link_to_add_association 'add user with usertype', f, :event_users
.actions
= f.submit 'Save'
(i ignore errors for now)
Then, you will need to specify the partial _event_user_fields.html.haml partial (here comes a little bit of magic) :
.nested-fields
= f.inputs do
= f.input :user_type, :as => :hidden, :value => 'participating'
- if f.object.new_record?
- f.object.build_user
= f.fields_for(:user, f.object.user, :child_index => "new_user") do |builder|
= render("user_fields", :f => builder, :dynamic => true)
and to end the _user_fields partial (which does not really have to be a partial)
.nested-fields
= f.inputs do
= f.input :name
This should work.
Do note that i had to update the formtastic_cocoon gem, so you will need to update to version 0.0.2.
Now it would be easily possible to select the user_type from a simple dropdown, instead of a hidden field, e.g. use
= f.input :user_type, :as => :select, :collection => ["Participator", "Organizer", "Sponsor"]
Some thoughts (now i proved it works):
this will always create new users on the fly, actually eliminating the need for the EventUser. Will you allow selecting existing users from a dropdown too?
personally i would turn it around: let users assign themselves to an event!

Does the events_users model not have an ID column? Since there's an additional field (user_type) then EventUser is a model and should probably have an ID. Maybe that's why user_type isn't being set in your first case.

Related

accepts_nested_attributes_for with has_many polymorphic

I use paperclip to upload multi-file attached to studentcourseassignment,but i fail.
model
class StudentCourseAssignment < ActiveRecord::Base
attr_accessible :score, :comment, :finish_status,:attachments
accepts_nested_attributes_for :attachments
belongs_to :assignment
belongs_to :user
has_many :attachments ,:as => :attachmentable,:dependent => :destroy
end
class Attachment < ActiveRecord::Base
attr_accessible :user_upload
belongs_to :attachmentable , :polymorphic => true
has_attached_file :user_upload
end
controller
**new**
#sca = StudentCourseAssignment.new
#sca.attachments.build
#sca.attachments.build
**create**
#sca = StudentCourseAssignment.new(params[:student_course_assignment])
#assignment = Assignment.find(params[:assignment_id])
#sca.user = current_user
#sca.assignment = #assignment
if #sca.save
flash[:alert] = "success"
redirect_to course_years_path
else
flash[:alert] = "fail"
redirect_to course_years_path
end
** view**
<%= form_for #sca, :url => assignment_student_course_assignments_path(#assignment),
:html => { :id => 'student-assignment-form', :multipart => true } do |f| %>
file:
<%= f.fields_for :attachments do |a_f| %>
<%= a_f.file_field :user_upload %>
<%= submit_tag "create" %>
<% end%>
<% end %>
wrong
No association found for name `attachments'. Has it been defined yet?
if remove accepts_nested_attributes_for :attachments,it's still wrong
Attachment(#70201401779680) expected, got Array(#70201383294620)
hope your help!thx!
Change
from:
attr_accessible :score, :comment, :finish_status,:attachments
to:
attr_accessible :score, :comment, :finish_status,:attachments_attributes
I realize this is an old question, but fwiw I think you'll need to move
accepts_nested_attributes_for :attachments
to appear after
has_many :attachments, :as => :attachmentable,:dependent => :destroy
I hit this in a project once myself; pretty sure it boils down to accepts_nested_attributes_for expecting the relation to already be declared before its invoked.

Rails: how to add user ownership of a picture album in the album/new form?

Hi I'm currently working on my first rails project, a site for users to make albums and upload pics. I have the registration, logging in, and friending installed into my app. I'm trying to make it so that in the album creation form, you can see a list of your friends and select who you want to share access to the album with (meaning whoever you select would also be part of #album.users. I'm planning on using a checkbox (I can't think of any better way) to make this selection. However, I am not sure how to link the friendship model with the album/new form. This is how my form looks like:
album/new.html.erb
<%= form_for ([#user, #album]), :html => { :id => "uploadform", :multipart => true } do |f| %>
<div class="formholder">
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.check_box :friends %>
<%= f.label :description %>
<%= f.text_area :description %>
<br>
<%=f.submit %>
</div>
<% end %>
an error occurs on line #6 (
<%= f.check_box :friends %>
the error:
undefined method 'friends' for #<Album:0x007fa3a4a8abc0>
I can understand why, but I don't know how to fix it. I have the typical friendship join model to add friends, and I want to be able to see a list of all the friends and select them. I think a following step would be to add something like #album.users << #user.friendships.find_by_name(params[:friends]) in the create action in the albums controller, but I don't know how I would loop through the form that only returns one param for friends?
Here are my files:
Albums controller create action:
def create
#user = User.find(params[:user_id])
#album = #user.albums.build(params[:album])
# not so sure about the following line.
#album.users << #user.friendships.find_by_name(params[:friends])
respond_to do |format|
if #user.save
format.html { redirect_to user_album_path(#user, #album), notice: 'Album was successfully created.' }
format.json { render json: #album, status: :created, location: #album}
else
format.html { render action: "new" }
format.json { render json: #album.errors, status: :unprocessable_entity }
end
end
end
album model
class Album < ActiveRecord::Base
attr_accessible :name, :description
validates_presence_of :name
has_many :album_users
has_many :users, :through => :album_user
has_many :photos
end
user model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :name, :password, :password_confirmation
validates_presence_of :password, :on => :create
validates_format_of :name, :with => /[A-Za-z]+/, :on => :create
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
validates_length_of :password, :minimum => 5, :on => :create
has_many :album_users
has_many :albums, :through => :album_users
accepts_nested_attributes_for :albums
has_many :friendships
has_many :friends, :through => :friendships
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
album_user model (join table to make many-to-many relationship between album, which has many users, and users, which has many albums)
class AlbumUser < ActiveRecord::Base
belongs_to :album
belongs_to :user
end
friendship model
class Friendship < ActiveRecord::Base
attr_accessible :friend_id
belongs_to :user
belongs_to :friend, :class_name => "User"
end
let me know if you need any more info!! Thanks in advance!!!
You should add users_ids (yes, two "s") to the list of accessible attributes of Album, and then use a "select multiple" on the :users_ids field.
<%= f.collection_select(:users_ids, User.all, :id, :name, :multiple => true) %>

Rails 3 - How to edit single nested polymorphic resource?

In my Rails 3.0 app I have a Client model and a polymorphic Addresses model. As per the code below a client can have many addresses. I would like to my form to update a single client address at a time. I can only seem to get the [addresses_attributes] to appear if I allow all of the client's addresses to be edited at the same time. Is there a way around this?
class Client < ActiveRecord::Base
has_many :addresses, :as => :addressable, :dependent => :destroy
accepts_nested_attributes_for :addresses
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
Clients Controller
def edit
#client = Client.find(params[:id])
#addresses = #client.addresses
if params[:address]
#address = #client.addresses.find(params[:address])
else
#addresses ? #address = #addresses.first : #address = []
end
end
def update
#client = Client.find(params[:id])
#client.update_attributes(params[:client])
redirect_to client_path(#client)
end
View
<%= form_for #client do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<%= f.fields_for #address do |addresses_attributes| %>
<%= render :partial => 'addresses/fields', :locals => {:f => addresses_attributes} %>
<% end %>
<%= f.submit %>
<% end %>
EDIT:
Sorry, I reread the post and realized that there were better options. You should add a scope to the Address model or you should create separate associations in the Client using conditions.
http://railscasts.com/episodes/108-named-scope
or see conditions here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
DETAIL:
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
named_scope :type_one, :conditions => { :address_type => 'one' }
named_scope :type_two, :conditions => { :address_type => 'two' }
<%= f.fields_for #address.type_one do |addresses_attributes| %>
<%= render :partial => 'addresses/fields', :locals => {:f => addresses_attributes} %>
<% end %>

Create/edit using a 3 column join table

As a previous post was asking (Alias table name to facilitate 3 column join table (MySQL or PostgreSQL)), I am working on a join table that joins 3 tables, Project, Employee, Role. The use of the :join_table => "my_join_table" works fine when I want to display information coming from the join tables. However, during the create action, the insert into SQL request is done in two times:
INSERT INTO properties_roles_users (project_id, employee_id) VALUES (11, 14)
and
INSERT INTO properties_roles_users (role_id, project_id) VALUES (5, 11)
instead of having only one insert into with the 3 fields.
I use the habtm relationship.
Any Idea how to get the INSERT INTO that would have all three ID (employee_id, project_id, role_id)?
[EDIT]
Ok, I was using part of the code from the other post so it would make sense in the context... we should then read "employees_projects_roles". Any who, let say I'll keep on going with user, property and role. Here's the code from my view:
<% form_for(#property,:html => { :multipart => true }) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :address %><br />
<%= f.text_field :address %>
</p>
( ... code ... )
<p>
<%= f.label :role, 'Role' %><br />
<%= f.collection_select :role_ids, Role.find(:all, :order => 'role'), :id, :role, {}, :multiple => true %>
</p>
<%= f.hidden_field :user_ids, :value => current_user.id %>
<%= render :partial => 'form', :locals => { :f => f } %>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
Controller (properties_controller:
def create
#property = Property.new(params[:property])
respond_to do |format|
if #property.save
flash[:notice] = 'Property was successfully created.'
format.html { redirect_to(#property) }
format.xml { render :xml => #property, :status => :created, :location => #property }
else
format.html { render :action => "new" }
format.xml { render :xml => #property.errors, :status => :unprocessable_entity }
end
end
end
Models:
class User < ActiveRecord::Base
has_and_belongs_to_many :properties, :join_table => "properties_roles_users"
has_and_belongs_to_many :roles, :join_table => "properties_roles_users"
(... other useful stuff ...)
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => "properties_roles_users"
has_and_belongs_to_many :properties, :join_table => "properties_roles_users"
end
class Property < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => "properties_roles_users"
has_and_belongs_to_many :roles, :join_table => "properties_roles_users"
attr_accessible (...:different_fields...) :user_ids, :role_ids
end
Got it. It seems that I needed to write an :insert_sql statement in the habtm declaration such as:
has_and_belongs_to_many :users,
:join_table => "projects_roles_users",
:foreign_key => 'role_id',
:association_foreign_key => 'project_id',
:insert_sql => 'INSERT INTO
projects_roles_users(project_id,role_id,user_id)
VALUES(#{id}, #{role_ids}, #{user_ids})'

Marking multi-level nested forms as "dirty" in Rails

I have a three-level multi-nested form in Rails. The setup is like this: Projects have many Milestones, and Milestones have many Notes. The goal is to have everything editable within the page with JavaScript, where we can add multiple new Milestones to a Project within the page, and add new Notes to new and existing Milestones.
Everything works as expected, except that when I add new notes to an existing Milestone (new Milestones work fine when adding notes to them), the new notes won't save unless I edit any of the fields that actually belong to the Milestone to mark the form "dirty"/edited.
Is there a way to flag the Milestone so that the new Notes that have been added will save?
Edit: sorry, it's hard to paste in all of the code because there's so many parts, but here goes:
Models
class Project < ActiveRecord::Base
has_many :notes, :dependent => :destroy
has_many :milestones, :dependent => :destroy
accepts_nested_attributes_for :milestones, :allow_destroy => true
accepts_nested_attributes_for :notes, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Milestone < ActiveRecord::Base
belongs_to :project
has_many :notes, :dependent => :destroy
accepts_nested_attributes_for :notes, :allow_destroy => true, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Note < ActiveRecord::Base
belongs_to :milestone
belongs_to :project
scope :newest, lambda { |*args| order('created_at DESC').limit(*args.first || 3) }
end
I'm using an jQuery-based, unobtrusive version of Ryan Bates' combo helper/JS code to get this done.
Application Helper
def add_fields_for_association(f, association, partial)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(partial, :f => builder)
end
end
I render the form for the association in a hidden div, and then use the following JavaScript to find it and add it as needed.
JavaScript
function addFields(link, association, content, func) {
var newID = new Date().getTime();
var regexp = new RegExp("new_" + association, "g");
var form = content.replace(regexp, newID);
var link = $(link).parent().next().before(form).prev();
if (func) {
func.call();
}
return link;
}
I'm guessing the only other relevant piece of code that I can think of would be the create method in the NotesController:
def create
respond_with(#note = #owner.notes.create(params[:note])) do |format|
format.js { render :json => #owner.notes.newest(3).all.to_json }
format.html { redirect_to((#milestone ? [#project, #milestone, #note] : [#project, #note]), :notice => 'Note was successfully created.') }
end
end
The #owner ivar is created in the following before filter:
def load_milestone
#milestone = #project.milestones.find(params[:milestone_id]) if params[:milestone_id]
end
def determine_owner
#owner = load_milestone || #project
end
Thing is, all this seems to work fine, except when I'm adding new notes to existing milestones. The milestone has to be "touched" in order for new notes to save, or else Rails won't pay attention.
This is bug #4242 in Rails 2.3.5 and it has been fixed in Rails 2.3.8.
i think your models are wrong. the notes have no direct relationship to project. they are through milestones.
try these
class Project < ActiveRecord::Base
has_many :milestones, :dependent => :destroy
has_many :notes, :through => :milestones
accepts_nested_attr ibutes_for :milestones, :allow_destroy => true
end
class Milestone < ActiveRecord::Base
belongs_to :project
has_many :notes, :dependent => :destroy
accepts_nested_attributes_for :notes, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Note < ActiveRecord::Base
belongs_to :milestone
end
Update: here is the code that worked for me based on the new info:
## project controller
# PUT /projects/1
def update
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
redirect_to(#project)
else
render :action => "edit"
end
end
# GET /projects/1/edit
def edit
#project = Project.find(params[:id])
#project.milestones.build
for m in #project.milestones
m.notes.build
end
#project.notes.build
end
## edit.html.erb
<% form_for(#project) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :notes do |n| %>
<p>
<div>
<%= n.label :content, 'Project Notes:' %>
<%= n.text_area :content, :rows => 3 %>
</div>
</p>
<% end %>
<% f.fields_for :milestones do |m| %>
<p>
<div>
<%= m.label :name, 'Milestone:' %>
<%= m.text_field :name %>
</div>
</p>
<% m.fields_for :notes do |n| %>
<p>
<div>
<%= n.label :content, 'Milestone Notes:' %>
<%= n.text_area :content, :rows => 3 %>
</div>
</p>
<% end %>
<% end %>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>

Resources