Why is my nested attributes form not working in rails? - ruby-on-rails

I have a model called profile that has_many jobs. I am using the cocoon gem in order to allow users to make a profile, and then on a separate page make as many jobs as they'd like. The profile form is working fine. The job form, however, doesn't seem to actually be creating the jobs. Since a user needs to fill out a profile form before they can fill out a jobs form, by the time they get to the jobs form, it will automatically go to the update action in the profiles controller instead of the create. I'm pretty sure the problem is in the profiles controller. Here is the profiles controller:
def new
if current_user.profile
redirect_to edit_profile_path(current_user.profile_name)
else
#profile = Profile.new
end
end
def create
#profile = current_user.build_profile(profile_params)
#profile.save
if current_user.profile.invalid?
render :new, :status => :unprocessable_entity
else
redirect_to profile_path(current_user.profile_name)
end
end
def edit
#profile = current_user.profile
end
def update
#if current_user.profile.jobs.any?
#profile_save = 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
private
def profile_params
params.fetch(:profile, {}).permit(:title,
:category, :description, :state, :zip_code, :rate,
jobs_attributes: [:firm, :position, :category, :description,
:begin, :end, :_destroy])
end
I use fetch instead of require because otherwise I received an error saying the profile was not found. Here is the form:
<%= simple_form_for #profile do |f| %>
<h3> Jobs </h3>
<%= f.simple_fields_for :jobs do |job| %>
<%= render 'job_fields', :f => job %>
<% end %>
<%= link_to_add_association 'add job', f, :jobs %>
<%= f.submit %>
<% end %>
And here is the job_fields partial:
.nested-fields
<%= f.input :firm, label: "Firm" %> <br>
<%= f.input :position, label: "Position" %> <br>
<%= f.input :category, label: "Category"%><br>
<%= f.input :begin, label: "Beginning", collection: 1960..2013 %><br>
<%= f.input :end, label: "End", collection: 1960..2013 %>
<%= f.input :description, label: "Description"%><br>
<%= link_to_remove_association "remove task", f %>
The problem could also be that I translated from HAML to ERB and I think I did it incorrectly.
Also, all profiles actually belong to a user, but i don't think that should make a difference. Thanks in advance for the help!

If the issue is that you want to call the create method for Jobs, you'll need to modify your form to explicitly use the post method. Something like:
<%= simple_form_for #profile, :url => new_jobs_path, :method => :post do |f| %>
<h3> Jobs </h3>
<%= f.simple_fields_for :jobs do |job| %>
<%= render 'job_fields', :f => job %>
<% end %>
<%= link_to_add_association 'add job', f, :jobs %>
<%= f.submit %>
<% end %>

Related

DRY controller .valid? for create and update using simple_form

So I've got these views:
new.html.erb
<div class="booyah-box col-xs-10 col-xs-offset-1">
<h1>Expose Your Hidden Gem</h1>
<%= simple_form_for #place do |f| %>
<%= f.input :name, error: "Name is mandatory" %>
<%= f.input :address %>
<%= f.input :description %>
<br />
<%= f.submit 'Create', class: 'btn btn-primary' %>
<% end %>
</div>
edit.html.erb
<div class="booyah-box col-xs-10 col-xs-offset-1">
<h1>Edit Your Place</h1>
<%= simple_form_for #place do |f| %>
<%= f.input :name %>
<%= f.input :address %>
<%= f.input :description %>
<br />
<%= f.submit 'Update', class: 'btn btn-primary' %>
<% end %>
</div>
this model:
Place.rb
class Place < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
has_many :photos
geocoded_by :address
after_validation :geocode
validates :name, presence: true
validates :address, presence: true
validates :description, presence: true
end
And finally, places_controller.rb (only showing create and update)
def create
#place = current_user.places.create(place_params)
if #place.save
redirect_to root_path
else
render :new
end
end
def update
#place = Place.find(params[:id])
if #place.user != current_user
return render text: 'Not Allowed', status: :forbidden
end
#place.update_attributes(place_params)
if #place.save
redirect_to root_path
else
render :edit
end
end
But, I'm trying to think DRY and want to know if there is a better way to do a validation for name address and description presence without having the same identical code in both the create and update portions of my controller? I feel like I should just be writing it once...
First, you can refactor your views to use the following structure:
# new.html.erb
<div class="booyah-box col-xs-10 col-xs-offset-1">
<h1>Expose Your Hidden Gem</h1>
<%= render 'form' %>
</div>
# edit.html.erb
<div class="booyah-box col-xs-10 col-xs-offset-1">
<h1>Edit Your Place</h1>
<%= render 'form' %>
</div>
# _form.html.erb
<%= simple_form_for #place do |f| %>
<%= f.input :name %>
<%= f.input :address %>
<%= f.input :description %>
<br />
<% submit_label = #place.new_record? ? 'Create' : 'Update' %>
<%= f.submit submit_label, class: 'btn btn-primary' %>
<% end %>
And then in your controllers you could refactor to:
def create
#place = current_user.places.new(place_params)
if #place.save
redirect_to root_path
else
render :new
end
end
def update
#place = current_user.places.find(params[:id])
#place.attributes = place_params
if #place.save
redirect_to root_path
else
render :edit
end
end

Rails passing params to different model and then saving it not working

I have a 'post' controller in that I have two variable title and body which I am passing through strong parameters.But I need to use two other variable which are path and name which are in different model name 'Document'..And also I am saving the content in database ..but unable to do so..getting this error view [posts/_form.html.erb]
undefined method `name' for #
[posts_controller]
class PostsController < ApplicationController
before_action :authenticate_user!
def index
#posts = Post.user_post(current_user).order('created_at DESC').paginate(:page => params[:page], :per_page => 5)
end
def new
#post = Post.new
end
def show
#post = find_params
end
def create
#post = Post.create(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
def edit
#post = find_params
end
def update
#post = find_params
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = find_params
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :body)
Document.new(params,:files=>[])
end
def find_params
Post.find(params[:id])
end
end
[post/_form.html.erb]
<%= form_for #post,html: { multipart: true } do |f| %>
<% if #post.errors.any? %>
<div id="errors">
<h2><%= pluralize(#post.errors.count, "error") %> prevented this post from saving:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :title %><br>
<%= f.text_field :title %><br>
<br>
<%= f.label :body %><br>
<%= f.text_field :body %><br>
<br>
<%= f.label :name %> <br>
<%= f.text_field :name %><br>
<br>
<br>
<%= f.label :path %><br>
<%= f.file_field :path %><br>
<%= f.submit %>
<% end %>
[document.rb]
class Document < ActiveRecord::Base
validates :name, presence: true
validates :path, presence: true
validates :resource_type, presence: true
validates :resource_id, presence: true
mount_uploader :path, PathUploader
validates :name, presence: true
# def self.abc
# params.permit(:name,:path)
# end
def initialize(params,file)
params=file[:name]
#params.permit(name =>:name,path =>:path)
end
end
undefined method `name' for #
You're referencing a non-existent attributes for your Post form:
<%= form_for #post,html: { multipart: true } do |f| %>
<%= f.label :title %><br>
<%= f.text_field :title %><br>
<br>
<%= f.label :body %><br>
<%= f.text_field :body %><br>
<%= f.submit %>
<% end %>
Remove :name & :path references.
--
If you want to pass "extra" attributes to another model, you need to use accepts_nested_attributes_for or set the params separately to your "primary" model:
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :documents
accepts_nested_attributes_for :documents
end
#app/models/document.rb
class Document < ActiveRecord::Base
belongs_to :post
end
This will allow you to pass the documents as "nested" attributes of your Post model:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
#post = Post.new
#post.documents.build
end
def create
#post = Post.new post_params
#post.save
end
private
def post_params
params.require(:post).permit(:title, :body, documents_attributes: [:name, :path])
end
end
#app/views/posts/_form.html.erb
<%= form_for #post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body %>
<%= f.fields_for :documents do |d| %>
<%= d.text_field :name %>
<%= d.text_field :path %>
<% end %>
<%= f.submit %>
<% end %>
So undefined method on a model will indicate that, well, the method doesn't exist on the model. Want to see a model's methods? Post.methods. However, in this example, the column name is not defined on the model., and you're trying to tell Post that it has a name. What you need to do is nest your parameters.
While there is a ton of cleaning up that might want to focus on first, your answer is found in the accepts_nestable_attributes_for class methods, as shown here, http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html, and strong_params documentation as shown here, http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
In your case, you want to create a new document from a post. Your permitted params hash will look like this,
params.require(:post).permit(:title, :body, :document_attributes => [:name])
Ensure that document_attributes is singular; if a person has_many pets (for example), then you'd have pets_attributes.
In your form, something that often trips people up is the builder.
<%= form_for #post do |f| %>
<%= f.text_field :title %>
<%= f.text_field :body %>
<%= f.fields_for #post.document do |document_field| %>
<%= document_field.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
Make sure that you're telling ERB that <%= f.fields_for %>, not just <% f.fields_for %>.

Rails 4 - select_tag does not pass id for field

I cannot pass the lecture_id to the created object:
Basically it should create the project with the lecture_id based on the dropdown menu. It does not seem to pass the data.
If for instance I add <%= f.input :lecture_id %> it will pass the data
<%= simple_form_for(#project) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :title %>
<%= select_tag(:lecture_id, options_for_select(#lecture_options)) %>
<%= f.input :company_name %>
<%= f.input :phone_number %>
<%= f.input :body %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Controller:
def new
#lecture_options = Lecture.all.map{|u| [u.title, u.id]}
#project = Project.new
respond_to do |format|
format.html
format.js
end
authorize #project
end
def create
#project = current_user.projects.build(project_params)
if #project.save
flash[:success] = "You have successfully created a project."
redirect_to profile_path(current_user)
else
render action: 'new'
end
authorize #project
end
def project_params
params.require(:project).permit(:company_name, :phone_number, :body, :user_id, :title, :lecture_id)
end
Models:
Lecture.rb
has_many :projects
Project.rb
class Project < ActiveRecord::Base
belongs_to :user
belongs_to :lecture
end
You should change select_tag to :
<%= select_tag("project[lecture_id]", options_for_select(#lecture_options)) %>

Ruby on Rails: hidden field value not inserting into database

I have two hidden_fields, user_id and skill_id
<%= form_for #skill do |s| %>
<%= s.label :image, "Upload your skill" %>
<%= s.hidden_field :user_id, value: current_user.id %>
<%= s.hidden_field :skill_id, value: params[:id] %>
<%= s.file_field :image, multiple: true %>
<% end %>
In my controller I have this:
def reviews
#skill = Skill.new
end
I'm able to get the value for skill_id into my database, but I'm not able to get the value from user_id. In my rails console, I see that user_id is being passed through "skill", but doesn't show in my database.
Parameters: {"utf8"=>"✓", "authenticity_token"=>"5+wxS929uxtt..", "skill"=>{"user_id"=>"7", "skill_id"=>"132", ...
I even checked to see if I'm getting any value with <%= current_user.id %>, which I am.
Maybe someone can guide me to the right path in debugging this issue.
Thanks
<%= form_for #skill do |s| %>
<%= s.label :image, "Upload your skill" %>
<%= s.hidden_field :user_id, value: current_user.id %>
<%= s.hidden_field :skill_id, value: params[:id] %>
<%= s.file_field :image, multiple: true %>
<% end %>
This will go to the create action
So what you really want is to update an existing one since you are passing an existing skills id
def reviews
#skill = Skill.find params[:id]
end
Now your form can use this
<%= form_for #skill do |s| %>
<%= s.label :image, "Upload your skill" %>
<%= s.hidden_field :user_id, value: current_user.id %>
<%= s.file_field :image, multiple: true %>
<% end %>
It is now go to the update action, in your controller.
def update
#skill = Skill.find params[:id]
#skill.update_attributes params[:skill]
redirect_to root_path # redirect to somewhere
end
Try this.
You need a create controller that would look something like this:
def create
#skill = Skill.new(params[:skill])
if #skill.save
# Handle a successful save.
else
render 'new'
end
end
You may want to change the name of the def reviews controller to def new, and also add the new routes to config/routes.rb

RoR wrong number of arguments (0 for 1) form

Hi I'm trying to create a form, that at the same time, creates a list and associates products to it.
The problem is that the form keeps raising
wrong number of arguments (0 for 1)
Extracted source (around line #10):
7: <%= f.text_area :description, placeholder:
8: "Compose a description for it ..." %>
9: </div>
10: <%= l.fields_for :products do |builder| %>
11: <%= render 'shared/product_form', :l => builder %>
12: <% end %>
13: <%= l.submit "Create", class: "btn btn-large btn-primary" %>
App Trace is
app/views/shared/_list_form.html.erb:10:in `block in _app_views_shared__list_form_html_erb__184644094_33330696'
app/views/shared/_list_form.html.erb:1:in `_app_views_shared__list_form_html_erb__184644094_33330696'
app/views/lists/new.html.erb:7:in `_app_views_lists_new_html_erb__973495114_33282228'
The code is as follows:
---view----
--list_form--
<%= form_for(#list) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder:
"Come up with a name for your list" %>
<%= f.text_area :description, placeholder:
"Compose a description for it ..." %>
</div>
<%= f.fields_for :products do |builder| %>
<%= render 'shared/product_form', :f => builder %>
<% end %>
<%= f.submit "Create", class: "btn btn-large btn-primary" %>
<% end %>
--product_form--
<%= f.text_field :name, "Name:" %>
<%= f.text_area :description, :rows => 3 %>
---model---
--list--
class List < ActiveRecord::Base
attr_accessible :description, :name
belongs_to :user
has_many :products, :dependent => :destroy
accepts_nested_attributes_for :products, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
has_many :list_categorization
has_many :category, :through => :list_categorization
validates :user_id, presence: true
validates :name, presence: true, length: {maximum: 10}
validates :description, length: {maximum: 140}
default_scope order: 'lists.created_at DESC'
def categorize!(category_id)
list_categorization.create!(category_id: category_id)
end
end
--product--
class Product < ActiveRecord::Base
attr_accessible :description, :donated, :name
validates :list_id, presence: true
belongs_to :list
end
---controllers---
--list_controller--
def new
#list = List.new
#products = #list.products.build
end
def create
#list = current_user.lists.build(params[:list]) if signed_in?
if #list.save
flash[:success] ="List " + #list.name + "created!"
render 'new'
end
--product_controller--
def new
#product = Product.new
end
def create
#product = #product.build(params[:product]) if signed_in?
if #product.save
flash[:success] ="Product " + #product.name + "created!"
end
You were right, I actually realized it after posting this, but now while trying to submit the form this happens:
The form contains 1 error.
* Name can't be blank
event tough I filled it correctly, this is what is getting passed
--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess
utf8: ✓
authenticity_token: 38CXjVORlj2RBgoTetIMoHomcVgOIlBU5rW3NTgkRkU=
list: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
name: list
description: this is a list
products_attributes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
'0': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
name: p1
description: this is a product
commit: Create
action: create
controller: lists
Where did that l come from? I'm pretty sure you need to change it to f:
<%= form_for(#list) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Come up with a name for your list" %>
<%= f.text_area :description, placeholder: "Compose a description for it ..." %>
</div>
<%= f.fields_for :products do |builder| %>
<%= render 'shared/product_form', :l => builder %>
<% end %>
<%= f.submit "Create", class: "btn btn-large btn-primary" %>
<% end %>
Update
There are a few problems with your code. First of all when you call #list = current_user.lists.build(params[:list]) if signed_in? it means that if there is no user signed in that object won't be created at all. The proper way to do something like this would be with a before_filter in your controller.
Secondly #product = #product.build(params[:product]) won't work. You haven't initialized a Product object yet, and you haven't assigned it to #product yet. Also build is used for associations. You need to change this to #product = Product.new(params[:product]).
Lists controller:
before_filter :user_signed_in? # add to products controller as well
# if you need this filter only on certain actions then do:
# before_filter :user_signed_in?, only: [:new, :create]
def new
#list = current_user.lists.build
#products = #list.products.build
end
def create
#list = current_user.lists.build(params[:list])
if #list.save
flash[:success] = "List " + #list.name + " created!"
redirect_to lists_path # this part was missing!
else # this was also missing
render 'new'
end # you had an 'if' with no 'end'
end
private
# add the following to Products controller as well, or if you
# use it a lot then place it in your application controller
def user_signed_in?
unless signed_in?
flash[:notice] = "You must first sign in"
redirect_to sign_in_path
end
end
Products controller:
def new
#product = Product.new
end
def create
#product = Product.new(params[:product]
if #product.save
flash[:success] = "Product " + #product.name + " created!"
redirect_to #product
else
render 'new'
end
end
As far as I remember however, the products#create action won't be used when saving a product through a nested form, the lists#create action will be used for both.
To learn more about nested forms have a look at these railscasts.
Once you've updated your code and gone through those videos, if you're still getting errors I would recommend to create a new question since this one is getting long and messy already :)
you forgot to do this:
rails generate migration add_remember_token_to_users

Resources