Related
The Application:
In my Rails (v3.2.17) project I have classes:
class User < ActiveRecord::Base
has_many :photos, dependent: :destroy
accepts_nested_attributes_for :photos, :allow_destroy => true
attr_accessible photos_attributes
and:
class Photo < ActiveRecord::Base
belongs_to :user
attr_accessible :name, :picture
has_attached_file :picture, :styles => { :medium => "370x370#", :thumb => "120x120#" }
validates :name, :presence => true
I use paperclip to handle the picture upload.
In the user form, I use the gem nested_form to dynamically create nested photo forms. My user form view goes like this:
<%= simple_nested_form_for(#user, :remote => true) do |f| %>
<%= f.error_notification %>
<div id="photos">
<%= f.fields_for(:photos) %>
</div>
<div class="add_new_photo_container">
<%= f.link_to_add(:photos, {:id => "add_new_photo", :data => {:target => "#photos"}}) do %>
Add photo
<% end %>
</div>
<%= f.button(:submit, #user.new_record? ? 'create' : 'update', :id => "submit_form") %>
<% end %>
Inside the form for nested_attribute photo, I have the file_field. As I want to submit user form using Ajax, and files can not be submitted through ajax, I am also using remotipart gem to handle this. My photo_fields is like this:
<%= f.link_to_remove(:class => "remove_photo_form") do %>
<%= f.file_field :picture, accept: 'image/png,image/gif,image/jpeg,image/jpg', :class => 'upload_picture' %>
<%= f.input(:name) %>
My controller has the create method:
def create
#user = User.new(params[:user])
if #user.save
respond_to do |format|
format.js {}
end
else
respond_to do |format|
format.js { render 'reload_form' }
end
end
end
And reload_form.js.erb goes like:
$('#user_form').replaceWith("<%= j (render 'users/form') %>");
What does work:
The success case, when I submit User with many photos and specify the name for each photo, does work perfectly.
The error case, when I do submit photos without name (name presence is required) but also without the picture file, does work fine. The form is reload and errors are showed in my html. This is the Rails log:
Started POST "/en/users" for 127.0.0.1 at 2015-06-23 22:31:18 -0300
Processing by UsersController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RmKyaIap0fJlIECa65oj0PLncYqxyAJ0OzPc7H3eiZg=", "user"=>{"photos_attributes"=>{"0"=>{"_destroy"=>"false", "name"=>""}, "1"=>{"_destroy"=>"false", "name"=>""}}, "starts_at"=>"", "finishes_at"=>""}, "commit"=>"Create", "locale"=>"en"}
What does not work:
The error case, when I do submit photos without name but I upload the picture file, does NOT work.
In this case, remotipart acts to submit the picture in an ajax manner. But there is some sort of error when reloading the f.link_to_add from nested_form. No errors are showed, but the form do not reload either.
And this is the Rails log of Post when it does not work:
Started POST "/en/users" for 127.0.0.1 at 2015-06-23 22:31:53 -0300
Processing by UsersController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RmKyaIap0fJlIECa65oj0PLncYqxyAJ0OzPc7H3eiZg=", "user"=>{"photos_attributes"=>{"0"=>{"_destroy"=>"false", "name"=>"", "picture"=>#<ActionDispatch::Http::UploadedFile:0x43d5a40 #original_filename="avatar.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"user[photos_attributes][0][picture]\"; filename=\"avatar.jpg\"\r\nContent-Type: image/jpeg\r\n", #tempfile=#<File:C:/Users/EVEDOV~1/AppData/Local/Temp/RackMultipart20150623-1552-ka9clf>>}, "1"=>{"_destroy"=>"false", "name"=>""}}, "starts_at"=>"", "finishes_at"=>""}, "commit"=>"Create", "remotipart_submitted"=>"true", "X-Requested-With"=>"IFrame", "X-Http-Accept"=>"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01", "locale"=>"en"}
When I enable exhibition of all kind of errors in Firebug console, that's what I see:
Handler function DebuggerProgressListener.prototype.onStateChange threw an exception: [Exception... "Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsIWebProgress.DOMWindow]" nsresult: "0x80004002 (NS_NOINTERFACE)" location: "JS frame :: resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/devtools/server/main.js -> resource://gre/modules/devtools/server/actors/webbrowser.js :: DPL_onStateChange :: line 1439" data: no]Line: 1439, column: 0
DevToolsUtils.js (linha 60)
<Sistema>
[Exception... "Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsIWebProgress.DOMWindow]" nsresult: "0x80004002 (NS_NOINTERFACE)" location: "JS frame :: chrome://browser/content/browser.js :: TabsProgressListener.onStateChange :: line 13564" data: no]
tabbrowser.xml (linha 490)
TypeError: can't access dead object
htmlPanel.js (linha 1082, col 16)
<Sistema>
Does anyone have faced any similar problem and knows how to fix it?
I have also tried to replace nested_form by cocoon gem, but I fall into the same problem.
I have event and band models, which have a many-to-many relationship through event_bands. I am trying to change my create method to use chosen jQuery like in episode 258 of railscasts. I am not sure how to read the message from my localhost when I try to create an event:
Started POST "/events" for 127.0.0.1 at 2014-03-16 17:11:07 +0900
Processing by EventsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Jx1Cm09uCwJcnY8573ZTRKMjH1BHWhlREFCfhij/AB0=", "event"=>{"name"=>"pojpj", "ko_name"=>"", "band_ids"=>["", "110"], "venue_id"=>"", "date(1i)"=>"2014", "date(2i)"=>"3", "date(3i)"=>"16", "time"=>"", "contact"=>"", "facebook"=>"", "ticket"=>"true", "price"=>"", "door_price"=>"", "ticket_url"=>"", "info"=>"", "info_ko"=>""}, "commit"=>"등록", "locale"=>"ko"}
Band Load (0.2ms) SELECT "bands".* FROM "bands" WHERE "bands"."id" = ? LIMIT 1 [["id", 110]]
(0.1ms) begin transaction
Band Exists (1.0ms) SELECT 1 AS one FROM "bands" WHERE ("bands"."name" = '...Whatever That Means' AND "bands"."id" != 110) LIMIT 1
(0.2ms) rollback transaction
Redirected to http://localhost:3000/events/new
It looks like it fails because the Band already exists in the database, but why is it doing that instead of creating the relation?
def new
#event = Event.new
end
def create
#event = Event.new(event_params)
if #event.save
flash[:notice] = "Event Created!"
redirect_to event_path(#event)
else
flash[:notice] = "Event not created!"
redirect_to new_event_path
end
end
private
def event_params
params.require(:event).permit(:name,
:ko_name,
:avatar,
:time,
:facebook,
:ticket,
:door_price,
:ticket_url,
:info_kr,
:contact,
:price,
:info,
:info_ko,
:venue_id,
:date,
band_ids: [])
end
Relevant part of the form:
<p>
<%= f.label "Bands" %><br />
<%= f.collection_select :band_ids, Band.order(:name), :id, :name, {}, {multiple: true} %>
</p>
I have accepts_nested_attributes_for :bands in the model and I think the relation is setup correctly because I can do a = Event.new(name: 'asdf', band_ids: [1,5]) a.save and it is persisted.
update: it seems the problem is coming from the empty item in band_ids. For some reason, rails is setting the param as band_ids: ['',3,5]. I can replicate the SQL message from my server by trying to create a new entry in the console like this: a = Event.create(name: 'asdfasdfasdfasdf2345', band_ids: ['', 3, 2]). But where is the empty first item coming from?
edit2: Disregard the above, it seems the problem is actually that there is no event_id to use in the association?
#messages={:"event_bands.event_id"=>["에 내용을 입력해 주세요"]}
I have a Comment form that also contains an Attachment form.
Comment model contains:
accepts_nested_attributes_for :attachments
Comment form contains:
<%= f.fields_for :attachments do |builder| %>
<%= builder.input :name, :label => 'Attachment Name' %>
<%= builder.file_field :attach %>
<% end %>
Comment Controller contains:
def new
#comment = Comment.new
#comment.attachments.build
If the user adds an Attachement, everything works fine.
I would like the user to be able to submit a Comment with or without an Attachment.
Right now, if the user enters a Comment without an attachment, the form re-displays and the Comment does not get created.
This is the log if I try to post a new Comment without an Attachement:
Started POST "/comments" for 127.0.0.1 at 2013-12-19 10:34:31 -0700
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"A6MOeMgoGUDmGiJr9PWinHVTAa7X63fgtA7+2my0A2Y=", "comment"=>{"user_id"=>"1", "status_date"=>"2013-12-19", "worequest_id"=>"10", "statuscode_id"=>"", "comments"=>"test", "attachments_attributes"=>{"0"=>{"name"=>""}}}, "_wysihtml5_mode"=>"1", "commit"=>"Save Comment"}
Tenant Load (0.3ms) SELECT "tenants".* FROM "tenants" WHERE "tenants"."subdomain" = 'ame' LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."tenant_id" = 1 AND "users"."id" = 1 LIMIT 1
(0.1ms) BEGIN
(0.1ms) ROLLBACK
I need to figure out the right code so that the Attachment fields show up in the form, but the Comment will get created if no Attachment is selected.
Maybe I need to put code in the Attachment controller?
You could use Rails present? method to check if the object is not blank:
#comment.attachments.build if #comment.attachments.present?
I changed the Comment model to this:
accepts_nested_attributes_for :attachments, :reject_if => lambda { |a| a[:attach].blank? }, :allow_destroy => true
I have a form that enables a user to update multiple alert rules records at the same time. But each alert rule record can have many notification emails. So I would like to be able to update alert rules and the associated notification emails within the same form.
Models:
class AlertRule < ActiveRecord::Base
has_many :notification_emails, :dependent => :destroy
accepts_nested_attributes_for :notification_emails, :reject_if => lambda { |notification| notification[:email].blank? }
end
class NotificationEmail < ActiveRecord::Base
belongs_to :alert_rule
end
In one of my controllers, I send an array of alerts to a form:
def alerts_config
//more code
#alert_rules = alert_rules.flatten
#alert_rules.each { |a| a.notification_emails.build }
render :partial => 'home/alerts_config', :layout => false
else
Then in my form, I want to allow the user to update alert rules and the associated email notifications:
= form_for #alert_rules, :url => '/home/save_alerts_config/' + #unit.id.to_s, :remote => true, :class => 'ajaxForm' do |f|
%table.scrollTable{:cellspacing => "0", :width => "740px", :style => "border-collapse: collapse; border-spacing: 0;"}
%thead.fixedHeader
%tr
%th Alerts
%th Enable
%th Primary Email
%th Notification Emails
%th
%tbody.scrollContent
- for rule in #alert_rules
%tr
%td= rule.alert_code.name
%td= check_box_tag "enabled_ids[]", rule.id
%td= f.text_field :email, :value => rule.email, :index => rule.id
%td.fields
= f.fields_for :notification_emails, rule.notification_emails do |notification_builder|
= notification_builder.text_field :email
= notification_builder.hidden_field :_destroy
= link_to_function 'Remove Notification', 'remove_notifications(this)'
.save_panel
= submit_tag "Save", :class => 'submit myButton'
When I submit to server, this is what I get:
Started POST "/home/save_alerts_config/6243" for 127.0.0.1 at 2012-03-20 17:49:06 -0400
Processing by HomeController#save_alerts_config as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"NPwuKuWippYjm2tJcfQI+/x9oEBwcR2rxcfpZMTO/Qo=", "enabled_ids"=>["51"], "alert_rule"=>{"51"=>{"email"=>"hythy"}, "notification_emails_attributes"=>{"0"=>{"email"=>"rtyrytry", "_destroy"=>"false"}, "1"=>{"email"=>"", "_destroy"=>"false"}, "2"=>{"email"=>"", "_destroy"=>"false"}, "3"=>{"email"=>"", "_destroy"=>"false"}, "4"=>{"email"=>"", "_destroy"=>"false"}, "5"=>{"email"=>"", "_destroy"=>"false"}, "6"=>{"email"=>"", "_destroy"=>"false"}, "7"=>{"email"=>"", "_destroy"=>"false"}, "8"=>{"email"=>"", "_destroy"=>"false"}, "9"=>{"email"=>"", "_destroy"=>"false"}, "10"=>{"email"=>"", "_destroy"=>"false"}, "11"=>{"email"=>"", "_destroy"=>"false"}, "12"=>{"email"=>"", "_destroy"=>"false"}, "13"=>{"email"=>"", "_destroy"=>"false"}, "14"=>{"email"=>"", "_destroy"=>"false"}, "15"=>{"email"=>"", "_destroy"=>"false"}, "16"=>{"email"=>"", "_destroy"=>"false"}, "17"=>{"email"=>"", "_destroy"=>"false"}, "18"=>{"email"=>"", "_destroy"=>"false"}, "19"=>{"email"=>"", "_destroy"=>"false"}, "20"=>{"email"=>"", "_destroy"=>"false"}, "21"=>{"email"=>"", "_destroy"=>"false"}, "22"=>{"email"=>"", "_destroy"=>"false"}, "23"=>{"email"=>"", "_destroy"=>"false"}, "24"=>{"email"=>"", "_destroy"=>"false"}, "25"=>{"email"=>"", "_destroy"=>"false"}, "26"=>{"email"=>"", "_destroy"=>"false"}, "27"=>{"email"=>"", "_destroy"=>"false"}, "28"=>{"email"=>"", "_destroy"=>"false"}, "29"=>{"email"=>"", "_destroy"=>"false"}, "30"=>{"email"=>"", "_destroy"=>"false"}, "31"=>{"email"=>"", "_destroy"=>"false"}, "32"=>{"email"=>"", "_destroy"=>"false"}, "33"=>{"email"=>"", "_destroy"=>"false"}, "34"=>{"email"=>"", "_destroy"=>"false"}, "35"=>{"email"=>"", "_destroy"=>"false"}, "36"=>{"email"=>"", "_destroy"=>"false"}, "37"=>{"email"=>"", "_destroy"=>"false"}, "38"=>{"email"=>"", "_destroy"=>"false"}, "39"=>{"email"=>"", "_destroy"=>"false"}, "40"=>{"email"=>"", "_destroy"=>"false"}}, "52"=>{"email"=>"yutu"}, "53"=>{"email"=>"ytuytu"}, "54"=>{"email"=>""}, "55"=>{"email"=>""}, "56"=>{"email"=>""}, "57"=>{"email"=>""}, "58"=>{"email"=>""}, "59"=>{"email"=>""}, "60"=>{"email"=>""}, "61"=>{"email"=>""}, "62"=>{"email"=>""}, "63"=>{"email"=>""}, "64"=>{"email"=>""}, "65"=>{"email"=>""}, "66"=>{"email"=>""}, "67"=>{"email"=>""}, "68"=>{"email"=>""}, "69"=>{"email"=>""}, "70"=>{"email"=>""}, "71"=>{"email"=>""}, "72"=>{"email"=>""}, "73"=>{"email"=>""}, "74"=>{"email"=>""}, "75"=>{"email"=>""}, "76"=>{"email"=>""}, "77"=>{"email"=>""}, "78"=>{"email"=>""}, "79"=>{"email"=>""}, "80"=>{"email"=>""}, "81"=>{"email"=>""}, "82"=>{"email"=>""}, "83"=>{"email"=>""}, "84"=>{"email"=>""}, "85"=>{"email"=>""}, "86"=>{"email"=>""}, "87"=>{"email"=>""}, "88"=>{"email"=>""}, "89"=>{"email"=>""}, "90"=>{"email"=>""}, "91"=>{"email"=>""}}, "commit"=>"Save", "id"=>"6243"}
This shouldnt be. As I show in the controller above, I only build one notification per alert, so why it sends all the notifications back as if they are all associated with the first alert, when I check the first alert only, is beyond me.
update:
even when I used create! instead of build to actually write the associated record, it still presented same problem: unable to get the alert id inside the name attribute of the notification_email input field
Thanks for response
You're not going to like it. But this is what I did to make it work...
Information based on this stackoverflow question and was heavily influenced by this pretty awesome answer I can up with this:
models/alert_rules_set.rb
# NOTICE: I'm not inheriting from ActiveRecord::Base
class AlertRulesSet
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :alert_rules
def alert_rules_attributes=(attributes)
# I'm tricking "fields_for" here.
# This should never actually be called
raise
end
# Strip fields_for indexes and return an array
def self.alert_rules_from(collection_set_hash)
collection_set_hash[:alert_rules_attributes].values
end
def persisted?
false
end
end
Controller
def alerts_config
//more code
#alert_rules_set = AlertRulesSet.new
#alert_rules_set = AlertRules.all # Or Whatever
#alert_rules_set.alert_rules.each { |a| a.notification_emails.build }
render :partial => 'home/alerts_config', :layout => false
end
def update_alerts_config
#alert_rules = AlertRulesSet.alert_rules_from(params[:alert_rules_set])
# Save Logic here
end
Form
= form_for #alert_rules_set, :url => '/home/save_alerts_config/' + #unit.id.to_s, :remote => true, :class => 'ajaxForm' do |f|
%table.scrollTable{:cellspacing => "0", :width => "740px", :style => "border-collapse: collapse; border-spacing: 0;"}
%thead.fixedHeader
%tr
%th Alerts
%th Enable
%th Primary Email
%th Notification Emails
%th
%tbody.scrollContent
= f.fields_for :alert_rules do |alert_rules_builder|
%tr
%td= rule.alert_code.name
%td= check_box_tag "enabled_ids[]", rule.id
%td= alert_rules_builder.text_field :email,
:value => rule.email, :index => rule.id
%td.fields
= alert_rules_builder.fields_for :notification_emails do |notification_builder|
= notification_builder.text_field :email
= notification_builder.hidden_field :_destroy
= link_to_function 'Remove Notification',
'remove_notifications(this)'.save_panel
= submit_tag "Save", :class => 'submit myButton'
Here's a link to a git branch where I got it working with another app and the commit compare.
This is crazy! What's going on?
So historically ActionPack tended to be pretty tightly coupled to ActiveRecord. This isn't a good idea for several reasons, so starting with Rails 3.0 ActiveModel was introduced. Unfortunately this is one of those cases that could probably be better.
ActiveModel
NOTE: Some of this is changing, specifically with this Rails 4 commit
So basically, I'm implementing the bare minimum to make this case work. Apart from the extend and include directives, the only thing that is necessary to make any other ActiveModel class play nice in this case seems to be persisted?.
As for everything else...
attr_accessor :alert_rules
This is just holding the alert_rules data. This is what fields_for is going to use to populate the fields with existing data.
alert_rules_attributes=(attributes)
This is kinda special. This is what fields_for checks for when it wants to know when to render a collection or not. Without this you will just get a single nested field(ie. [alert_rule][notification_emails_attributes]) instead of a collection of nested fields(ie. [alert_rule][notification_emails_attributes][1]).
self.alert_rules_from(collection_set_hash)
This is the more confusing part. So basically when you submit some nested attributes they're actually in a Hash with an index key.
nested_model_attributes = {
"0"=>{"attribute"=>"This", "id"=>"0"},
"1"=>{"attribute"=>"That"}
}
This method is a quick way to get rid of that and get usable data out (This happens in ActiveRecord too). I figured it would probably be easier to manipulate the data than try to do some saving magic in the AlertRuleSet class (but you could).
I have two models: Post and Image in a forum application where Posts are arranged in parent-child format using dm-is-tree. Up this point, the images had been part of the Post model. As the Post model gets unwieldy and I need to add more depth to notating the image, I'm working to spin off the Image into its own model, but is still part of the post in output.
So I started integrating dm-accepts_nested_attributes in a simple arrangement:
class Post
include DataMapper::Resource
property :id, Serial
property :istop, String
property :created_at, DateTime
property :updated_at, DateTime
property :content, Text
has n, :images
accepts_nested_attributes_for :images
is :tree, :order => [:istop, :created_at]
class Image
include DataMapper::Resource
property :id, Serial
property :created_at, DateTime
belongs_to :post
property :image, String, :auto_validation => false # Carrierwave image info
mount_uploader :image, ImageUploader # Carrierwave uploader
I have this form(haml) on every page for creating a post:
= form_for [#forum,Post.new], :html => {:multipart => true} do |f|
= f.hidden_field :istop, :value => "parent"
= f.text_area :content
= f.fields_for :simages_attributes do |g|
= g.file_field :image
.actions
= f.submit
That goes to this controller:
def create
#forum = Forum.get(params[:forum_id])
#post = #forum.posts.create(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(forum_path(#forum), :notice => 'Post was successfully created.') }
else
format.html { redirect_to(forum_path(#forum), :notice => 'There was an error in posting') }
end
end
end
The error I get when posting:
undefined method[]' for #`
, a NoMethodError
I'm not sure what I'm doing or where this is coming from at this point. I'm not sure if I have the form set-up correctly (I've been following similar active record tutorials and haven't delved into the dm-accepts_nested code at all yet). I'm able to set some even more basic stuff through the command line, but not images. I understand the basics of the nesting, but not really how to integrate it to what I'm doing atm.
Maybe someone knows. Any help appreciated.
attr_accessor :images_attributes in the Post model, allows the form to submit
However, the image is now not being saved, i.e. gets lost somewhere and isn't saved
The response I get from submitting the form:
Started POST "/forums/x/posts" for 127.0.0.1 at 2010-12-22 10:15:19 -0500
Processing by PostsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/cyeRIls9M..7U8eG1lXAJg8=", "post"=>{"istop"=>"parent", "content"=>"sdfgsdfg", "images_attributes"=>{"image"=>#<File:/tmp/RackMultipart20101222-874-bhvepi>}}, "commit"=>"Create Shout", "forum_id"=>"x"}
SQL (0.054ms) SELECT "name" FROM "forums" WHERE "name" = 'x' ORDER BY "name" LIMIT 1
SQL (115.419ms) INSERT INTO "posts" ("istop", "created_at", "updated_at", "forum_name") VALUES ('parent', '2010-12-22T10:15:20-05:00', '2010-12-22T10:15:20-05:00', '', 'sdfgsdfg', 0, 'x')
Redirected to http://localhost:3000/forums/x
Completed 302 Found in 123ms
I'm guessing the form is ok, but its not saving the image.
Adding
#post.images_attributes = Image.new
or some variation to the controller does nothing so I'm curious if I need to create some sort of hook in the Post model to save the image. I do not know at this point.