I suspect this might be a very simple mistake but I've spent 3 hours looking for it so I thought I might ask for some help from the community.
I'm running through Ryan Bates' excellent screencasts on Nested Models Forms and trying to apply them to my own project. The problem is the nested attribute doesn't seem to save using the form. I can get it to save through the console but it only shows up as empty brackets when going through the form.
Here's the relevant code:
The form view (using haml)
= form_for(#article) do |f|
- if #article.errors.any?
#error_explanation
%h2
= pluralize(#article.errors.count, "error")
prohibited this article from being saved:
%ul
- #article.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :title
%br/
= f.text_field :title
.field
= f.label :intro
%br/
= f.text_area :intro
= f.fields_for :subsections do |builder|
= render 'subsections_fields', :f => builder
.field
= f.label :published_at
%br/
= f.text_field :published_at
.actions
= submit_or_cancel(f)
subsection_fields form view
= f.label :header
%br/
= f.text_field :header
= f.label :order_id
= f.number_field :order_id
%br/
= f.label :body
%br/
= f.text_area :body
%br/
= f.check_box :_destroy
= f.label :_destroy, "Remove Subsection"
%br/
Controller
class ArticlesController < ApplicationController
def new
#article = Article.new
3.times { #article.subsections.build }
end
def create
#article = Article.new(params[:article])
if #article.save
flash[:notice] = "Successfully created article."
redirect_to #article
else
render :action => 'new'
end
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update_attributes(params[:article])
flash[:notice] = "Successfully updated article."
redirect_to #survey
else
render :action => 'edit'
end
end
def destroy
Article.find(params[:id]).destroy
flash[:notice] = "Succesfully destroy article."
redirect_to articles_url
end
def show
#article = Article.find(params[:id])
end
def index
#articles = Article.all
end
end
And the models
class Article < ActiveRecord::Base
attr_accessible :title, :intro
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end
class Subsection < ActiveRecord::Base
attr_accessible :header, :body, :order_id
belongs_to :article
validates :header, :presence => true
validates :body, :presence => true
end
Any help figuring this out is much appreciated.
I'm not quite sure, but try it with attr_accessible :article_id as well in your Subsection model?
Adding "attr_accessible" to a model changes the way mass assignment works in rails.
If you remove the "attr_accessible" lines in your models then all your code will work perfectly as it is.
The class method "accepts_nested_attributes_for" adds a "subsections_attributes=(value)" method to your model.
The second you add "attr_accessible" to a model you now are forced into adding extra "attr_accessible" entries for each field that you want to assign via mass assignment. i.e. when you use Article.new(params[:article]).
I hope that was clear.
I figured out the answer to this one from another question.
The answer was to set my subsections_attributes as an attr_accessible, so the above Article model should look like this (I also added published_at as an attr_accessible):
class Article < ActiveRecord::Base
attr_accessible :title, :intro, :subsections_attributes, :published_at
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end
Related
I have a job model that belongs_to a profile model. A profile has_many jobs. I have a nested model form that in which a user adds jobs. The form is successfully adding jobs, but editing/updating is not working. Instead, when I try to edit a job, it keeps the old version of the job, but adds the new version as well. It does not replace the old version with the new one. How do I fix this? I'm pretty sure it has something to do with the edit/update controllers.
Edit controller:
def edit
#profile = current_user.profile
end
Update controller:
def update
#if current_user.profile.jobs.any?
#profile = current_user.profile.update_attributes(profile_params)
if current_user.profile.invalid?
#profile = current_user.profile
render :edit, :status => :unprocessable_entity
else
redirect_to profile_path(current_user.profile_name)
end
end
The thing is, the update controller is working for the non-nested information, it is just not working for the nested jobs. Here are the strong parameters:
def profile_params
params.require(:profile).permit(:title,
:category, :description, :state, :zip_code, :rate,
jobs_attributes: [:firm, :position, :category, :description,
:begin, :end, :_destroy])
end
And here is the profile model:
class Profile < ActiveRecord::Base
belongs_to :user
has_many :jobs
accepts_nested_attributes_for :jobs , :reject_if => :all_blank, :allow_destroy => true
end
Also, here's my routes if that will help:
resources :profiles do
resources :jobs
end
Thanks for your help in advance.
EDIT
Here are the params from the create action:
{"jobs_attributes"=>{"1383593195849"=>{"firm"=>"1st firm", "position"=>"1st position",
"category"=>"1st category", "description"=>"1st description", "begin"=>"1999",
"end"=>"1999", "_destroy"=>"false"}}}
And here are the params for the same job when updated:
{"jobs_attributes"=>{"0"=>{"firm"=>"1st firm", "position"=>"1st position",
"category"=>"1st category", "description"=>"1st description", "begin"=>"1999",
"end"=>"1999", "_destroy"=>"false"}, "1"=>{"firm"=>"1st firm",
"position"=>"1st position", "category"=>"1st category",
"description"=>"1st description", "begin"=>"1999", "end"=>"1999", "_destroy"=>"1"}}}
EDIT:
Here are my views. I don't think they are part of the problem though.
= simple_form_for #profile do |f|
%h3 Jobs
#jobs
= f.simple_fields_for :jobs do |job|
= render 'job_fields', :f => job
.links
= link_to_add_association 'add job', f, :jobs
= f.submit
And here is the "job_fields" partial:
.nested-fields
= f.input :firm
= f.input :position
= f.input :category
= f.input :begin
= f.input :end
= f.input :description
= f.input :_destroy, as: :boolean, inline_label: 'Delete box'
= link_to_remove_association "remove task", f
The trick is adding the ':id' symbol to the strong params. Although I still haven't figured out why and I'm not sure if its secure.
So i want to save a HABTM relationship but i have extra field on my model so i'm usign has_many and through method.
Here's my models:
#project_task.rb
class ProjectTask < ActiveRecord::Base
attr_accessible :description, :name, :user_id, :project_id, :user_ids
belongs_to :project
belongs_to :user #Created by
has_many :project_task_users #Here is the HABTM
has_many :users, :through => :project_task_users #AND HERE
validates :name, :presence => true
validates :project_task_users, :length => { :minimum => 1} #must have atleast 1 record in the HABTM relation
accepts_nested_attributes_for :project_task_users
end
#project_task_user.rb
class ProjectTaskUser < ActiveRecord::Base
belongs_to :user
belongs_to :project_task
end
My form:
<p>Users:</p>
<% for user in #users %>
<div>
<%= check_box_tag "project_task[user_ids][]", user.id, #task.users.include?(user) %>
<%= user.name %> - <%= user.company.name %>
</div>
<% end %>
My controller:
GET
def new_task
#project = Project.find(params[:project_id])
#task = ProjectTask.new(:project_id => #project.id)
#users = #project.users
end
POST
def new_task_post
#project = Project.find(params[:project_id])
#task = ProjectTask.new params[:project_task]
#task.user_id = current_user.id
if #task.save
redirect_to #project
else
#users = #project.users
render action:"new_task"
end
end
When i submit, the #task.save returns false and the errors array with the project_task_users validation
Update
as Pablo89 pointed out i renamed my check_box_tag to this
<%= check_box_tag :user_ids, user.id, #task.users.include?(user), :name => 'project_task[user_ids][]' -%>
and it saves only if i comment out the validation. How should i validate that a user is selected while creating a project_task?
I have a Post model, which has many Post_Images (polymorphic as :imageable). I am trying to make a nested form where Posts can be created, including Post_Images, all while simultaneously assigning the post to groups (there is a group model which has_many posts :through :assignments, hence the form_for [#user, #post] below).
Models:
Post Model
class Post < ActiveRecord::Base
# attr_accessible :name, :borrow, :price, :description, :user_id, :product_category_id, :post_image_ids, :group_ids, :post_images_attributes, :post_image
has_many :post_images, :as => :imageable, :dependent => :destroy
accepts_nested_attributes_for :post_images, :reject_if => lambda { |t| t[:post_image].nil?}, :allow_destroy => true
.
.
.
end
PostImage model (should be just Image, but I made it ahile ago and can't refactor it):
class PostImage < ActiveRecord::Base
# attr_accessible :imageable_id, :imageable_type, :post_image_ids
belongs_to :imageable, :polymorphic => true
has_attached_file :image, :styles => { :small => "150x150>", :large => "320x240>" }
validates_attachment_presence :image
validates_attachment_size :image, :less_than => 5.megabytes
end
PostsController
def new
#user = User.find(params[:user_id])
#post = #user.posts.build
#assigment = Assignment.new
#allow up to 5 images to be uploaded
#5.times {#post.build_post_image}
post_image = #post.post_images.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
def create
#user = User.find(params[:user_id])
params[:post][:group_ids] ||= []
#post = #user.posts.build(params[:post])
if #post.save
redirect_to #user
else
redirect_to new_user_post_path(current_user)
end
end
The blasted form: (builds posts, while also adding them to groups)
=nested_form_for( [#user, #post], :remote => true) do |f|
=hidden_field_tag 'user_id', #user.id
=f.fields_for :post_images
=f.link_to_add "Add an image", :post_images
=f.label :name
=f.text_field :name
=f.label :borrow, "borrow?"
=f.check_box :borrow
=f.label :price
=f.text_field :price
=f.label :description
=f.text_area :description
%h2 choose which groups you want to post this item to
-#user.groups_as_owner.each do |group|
= label_tag "group_checkbox_#{group.id}", group.name
= check_box_tag("post[group_ids]", group.id, #user.groups_as_owner.include?(group), :id => "group_checkbox_#{group.id}")
-#user.groups_as_member.each do |group|
= label_tag "group_checkbox_#{group.id}", group.name
= check_box_tag("post[group_ids]", group.id, #user.groups_as_member.include?(group), :id => "group_checkbox_#{group.id}")
.action=f.submit "Save Post"
_post_image_fields partial
=f.label :post_image, "Image"
%br
=f.file_field :post_image
=f.link_to_remove "remove"
I have attr_accessible commented out because I was getting a WARNING: Can't mass-assign protected attributes and in searching for a resolutio to this, I happened on a bunch of resources including:
nested form with polymorphic models
google group convo addressing mass-assignment warning
railscasts nested form 2
To my knowledge, none of them really addressed my issue. Maybe it is because of the extra aspect I have in my form- the creation of group assignments? It seems to me though that it is a param issue. Because I got it to the point where the form would work...with a lot of attr_accessible in place, but it would say that it couldn't mass-assign to :post_image which I made accessible in PostImages and Posts...so I wasn't sure what the issue with that was.
lastly, is the answer in this post?(i don't quite understand it):
ActiveRecord, has_many :through, and Polymorphic Associations
Let me know if I should supply more info, somehow make this clearer. Happy Holidays all!
I think Im taking the wrong approach to this and have tried to find the best approach on the web but so far no luck.
I have a projects model, which has many messages and users. The messages belong to both projects and users (as displayed below). So I need to pass in both the project id and user id into the message form. I know this should be pretty straightforward, but Im obviously messing it up. Not sure at this stage wether using http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-hidden_field_tag is necessarily the best idea either.
Any help would be awesome.
Project Model:
class Project < ActiveRecord::Base
belongs_to :user
has_many :users
has_many :messages, :dependent => :destroy
end
User Model:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :first_name, :last_name, :username, :email, :password, :password_confirmation
has_many :projects
belongs_to :projects
has_many :messages
end
Message Model:
class Message < ActiveRecord::Base
attr_accessible :title, :message
belongs_to :project
validates :title, :presence => true
validates :message, :presence => true
end
Projects show:
def show
#project = Project.find(params[:id])
#title = #project.title
#curent_user = current_user
#message = Message.new
begin
#messages = #project.messages
rescue ActiveRecord::RecordNotFound
end
end
/shared/_message.html.erb
<%= form_for #message do |f| %>
<%= f.label :title %>:
<%= f.text_field :title %><br>
<%= f.label :message %>
<%= f.text_area :message %>
<%= f.submit %>
<% end %>
Message create action
def create
#message = #project.messages.build(params[:message])
if #message.save
flash[:success] = "Message created!"
redirect_to root_path
else
render 'pages/home'
end
end
Appreciate your time, just trying to identify how I transfer the user_id/project_id into the from field so it's passed in at message creation.
Set the project_id/user_id in the controller so they can't be modified by end users when submitting the forms.
As you're using #project.messages.build in the message controller create action the project_id should automatically be set.
You can then set the user with #message.user = #current_user
I am new to Ruby and Rails so sorry if this looks too noob.
I have created a resource called stream and another resource called tasks and have mapped them properly using has_many and belong_to. Everything works until I decided to add a "Quick Task Add form" on my Stream.show view:
Here is the view code for the form:
<%= form_for(#task) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<div class="field">
<%= f.text_field :title %> <%= f.submit "Add Task" %>
<%= hidden_field_tag(:stream_id, #stream.id) %>
</div>
<% end %>
Here is my Stream.show action:
def show
#stream = Stream.find(params[:id])
#user = User.find(#stream.user_id)
#tasks = #stream.tasks.paginate(:page => params[:page])
#title = #stream.title
#task = Task.new
end
And here is my task controller:
class TasksController < ApplicationController
def create
#stream = Stream.find(params[:stream_id])
#stream.tasks.create!({:title => params[:task][:title], :user_id => 1, :owner => 1})
if #stream.save
flash[:success] = "Task created succesfully!"
else
flash[:error] = "Error creating task"
end
redirect_to #stream
end
end
Looks pretty basic to me. The problem is when it executes tasks.create, I get the following error message: "Validation failed: User can't be blank, Owner can't be blank"
What am I doing wrong?
edit: adding model code from comment
class Stream < ActiveRecord::Base
attr_accessible :title
belongs_to :user
has_many :tasks, :dependent => :destroy
validates :title, :presence=> true, :length => { :maximum =>50 }
validates :user_id, :presence => true
end
class Task < ActiveRecord::Base
attr_accessible :title
belongs_to :stream
validates :title, :presence=> true, :length => { :maximum =>70 }
validates :user_id, :presence => true
validates :owner, :presence => true
validates :stream_id, :presence => true
default_scope :order => "updated_at"
end
You should set your user_id and owner fro STREAM object
class TasksController < ApplicationController
def create
#stream = Stream.find(params[:stream_id])
#stream.tasks.create!({:title => params[:task][:title], :user_id => 1, :owner => 1})
#stream.user_id = 1
#stream.owner = 1
if #stream.save
flash[:success] = "Task created succesfully!"
else
flash[:error] = "Error creating task"
end
redirect_to #stream
end
end
Unfortunately i can't test my suggestion currently but you might have to add
Attr_accessible :user,:owner
To the task model because you are mass-assigning these field using the hash.