submitting accepts_nested_attributes and failed validations in parent - ruby-on-rails

Thanks in advance for your help.
This may get a little long.
Setup
Models:
class Collection < ActiveRecord::Base
has_many :collection_ownerships
has_many :items
has_many :users, :through => :collection_ownerships
validates :title, presence: true, length: { maximum: 25 }
validates :description, length: { maximum: 100 }
end
class Item < ActiveRecord::Base
belongs_to :collection
has_many :item_ownerships, :dependent => :destroy
accepts_nested_attributes_for :item_ownerships
validates :name, :presence => true, length: { maximum: 50 }
validates :collection, :presence => true
end
class ItemOwnership < ActiveRecord::Base
belongs_to :item
belongs_to :user
validates :user, :presence => true
validates :item, :presence => true
end
Controllers
class ItemsController < ApplicationController
before_action :authenticate_user!
before_filter(:except => :toggle_item_owned_state) do
#collection = current_user.collections.find(params[:collection_id])
#item_list = #collection.items
end
def toggle_item_owned_state
#item = Item.find_by_id(params[:item_id])
#io = #item.item_ownerships.find_by_user_id(current_user)
#result = #io.update_attributes(:owned => #io.owned? ? false : true)
respond_to do |format|
if #result
format.html { }
format.js {}
else
format.html { }
format.js {}
end
end
end
def index
end
def new
#item = #collection.items.new
#item_ownership = #item.item_ownerships.build(:owned => true, :user => current_user, :item => #item)
end
def create
#item ||= #collection.items.new(item_params)
#item_ownership ||= #item.item_ownerships.build(:user => current_user, :item => item)
if #item.save
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "new"
end
end
def edit
#item = #collection.items.find_by_id(params[:id])
end
def update
#item = #collection.items.find_by_id(params[:id])
if #item.update_attributes(item_params)
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "edit"
end
end
def item_params
params.require(:item).permit(:name,
item_ownerships_attributes: [:id, :owned ])
end
end
View
<div class="row">
<span class="col-sm-12">
<div id="add_item">
<%= form_for [#collection, #item] do |f| %>
<div class="form-group <%= 'has-error has-feedback' if #item.errors[:name].present? %>">
<label class="sr-only" for="item_name">Item Name</label>
<%= f.text_field :name, :autofocus => true, :placeholder => "Item Name", :class => "form-control", :'aria-describedBy' => "itemNameBlock" %>
<% if #item.errors[:name].present? %>
<span id="itemNameBlock" class="error">Item <%= #item.errors[:name].first %></span>
<% end %>
<%= f.fields_for :item_ownerships do |io| %>
<%= io.check_box :owned %> Owned
<% end %>
</div>
<div id="signin_button_row">
<%= f.submit "Save", :class => "form-control green_button" %>
<span id="forgot_my_password" class="right-justify">
<%= link_to "cancel", collection_items_path(#collection), :class => "new_colors terms" %>
</span>
</div>
<% end %>
</div>
</span>
</div>
Symptom of problem:
When I submit the form in a failure situation (ie name is not provided), the form currently detects the validation and displays the error, however, I now get two checkboxes called 'owned' appearing in the form. Each failing submission adds an additional checkbox (as per attached image).
Can anyone help?
Update
This issue has taken a turn for the strange. I haven't changed anything (I know, I know, you don't believe it) other than restarting the server but now nothing is saved even with valid data. I am getting validation errors saying:
Validation failed: Item ownerships user can't be blank, Item ownerships item can't be blank

In your controller create action you have
def create
#item ||= #collection.items.new(item_params)
#item_ownership ||= #item.item_ownerships.build(:user => current_user)
if #item.save
redirect_to collection_items_path(#collection)
else
flash.now[:alert] = "There was a problem saving this item."
render "new"
end
end
Problem is on third line when you assign #item_ownership this variable is always empty at this time so code after = is executed. #item.item_ownerships.build(:user => current_user) always build new item_ownership and stores it into #item instance. So after first call of create you will have two item_ownerships first is from form and second is newly created. If validation fails and you send form again you will have three instances of item_ownerships two from the form a one newly created etc.

#edariedl was on the right track in addressing the initial response. The second line is completely unnecessary.
The second issue mentioned in the update I addressed in a separate question found here: Losing mind over validation failure saving nested models

Related

Rails call custom validation before .new or .create

I make objects in controller's loop.
I need to check pet_name array before loop starts.
(because i got undefined method 'each' for nil:NilClass when
params[:pet_name].each do |pid|) runs)
But my validation always called after User.new or User.create.
I want to change to validate as when i push submit button and check validation, and redirects back when pet_name array is nil.
Ho can i change my code?
Controller
def create
user_name = params[:user_name]
params[:pet_name].each do |pid|
#user = User.new
#user.name = user_name
#user.pet_name = pid
render :new unless #user.save
end
redirect_to users_path
end
User.rb
class User < ApplicationRecord
has_many :pet
validates :name, presence: true
validates :pet_name, presence: true
validate :check_pet
def check_pet
if pet_name.nil?
errors.add(:pet_name, 'at least one pet id')
end
end
end
Prams structure
{ name: 'blabla', pet_name: ['blabla', 'blablabla', 'bla'] }
Sorry but that isn't even close to how you approach the problem in Rails.
If you want a user to have many pets and accept input for the pets when creating users you need to create a working assocation to a Pet model and have the User accept nested attributes:
class User < ApplicationRecord
has_many :pets # has_many assocations should always be plural!
validates :name, presence: true
validates :pets, presence: true
accepts_nested_attributes_for :pets
end
# rails g model pet name user:reference
class Pet < ApplicationRecord
belongs_to :user
validates :name, presence: true
end
class UsersController < ApplicationController
def new
#user = User.new(user_params)
3.times { #user.pets.new } # seeds the form with blank inputs
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user,
success: 'User created',
status: :created
else
render :new,
status: :unprocessable_entity
end
end
private
def user_params
params.require(:user)
.permit(:name, pets_attributes: [:name])
end
end
<%= form_with(model: #user) do |form| %>
<div class="field">
<%= form.label :name %>
<%= form.text_input :name %>
</div>
<fieldset>
<legend>Pets</legend>
<%= form.fields_for(:pets) do |pet_fields| %>
<div class="nested-fieldset">
<div class="field">
<%= pet_fields.label :name %>
<%= pet_fields.text_input :name %>
</div>
</div>
<% end %>
</fieldset>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This is a pretty advanced topic and you should have your Rails CRUD basics well figured out before you attempt it. You should also consider if you instead want to use a separate controller to create the pets one by one as a nested resource after creating the user.

Rails nested form attributes not getting saved

I've already looked through every other stackoverflow for this issue, but none of the solutions have fixed this. My elements in a nested_form are not being saved in the database. I've also made sure that all model associations are correct. I've been trying to fix this for nearly 8 hours now, and would really appreciate some help, especially considering every other solution hasn't worked.
Basically, I have a Playlist model that contains multiple Song models. I'm trying to use a nested_form to add the Song models to the Playlist. However, none of the Songs are ever being saved. I apologize if my methods are misguides, as I'm still fairly new to Rails.
GitHub Repo:https://github.com/nsalesky/Ultra-Music
playlists_controller.rb
def index
#user = current_user
#playlists = #user.playlists
end
def show
#user = current_user
#playlist = #user.playlists.find(params[:id])
end
def new
#playlist = Playlist.new
#I was told to do this
#playlist.songs.build
end
def create
#user = current_user
#playlist = #user.playlists.create(playlist_params)
if #playlist.save
redirect_to #playlist
else
render :action => 'new'
end
end
def edit
#playlist = current_user.playlists.find(params[:id])
end
def update
#user = current_user
#playlist = #user.playlists.find(params[:id])
if #playlist.update_attributes(playlist_params)
redirect_to #playlist
else
render :action => 'edit'
end
end
def destroy
#user = current_user
#playlist = #user.playlists.find(params[:id])
#playlist.destroy
redirect_to playlists_path(#user.playlists)
end
private
def playlist_params
params.require(:playlist).permit(:name, :description, songs_attributes: [:id, :name, :link, :_destroy])
end
playlist.rb
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, :allow_destroy => true, :reject_if => lambda { |a| a[:content].blank? }
validates :name, presence: true
validates_associated :songs, presence: true
_form.html.erb
<%= nested_form_for #playlist do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<!--<div>
<button type="button" id="addsong">Add Song</button><br>
<button type="button" id="removesong">Remove Song</button><br>
</div> !-->
<div>
<%= f.fields_for :songs do |song_form| %>
<%= song_form.text_field :name %>
<%= song_form.text_field :link %>
<%= song_form.link_to_remove "Remove Song" %>
<% end %>
<p><%= f.link_to_add "Add Song", :songs %></p>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
In your playlist.rb, you wrote:
:reject_if => lambda { |a| a[:content].blank? }
Here the block parameter |a| stands for attributes of a specific song. So a[:attribute] relates to a single attribute. The problem is your Song doesn't have a :content attribute. So this a[:content].blank? will always be true, means you would be rejected building a song.
Just change a[:content] to a valid attribute such as a[:name]

Absolutely stuck trying to create nested association in rails form with has_many through

I posted an earlier question about this and was advised to read lots of relevant info. I have read it and tried implementing about 30 different solutions. None of which have worked for me.
Here's what I've got.
I have a Miniatures model.
I have a Manufacturers model.
Miniatures have many manufacturers THROUGH a Productions model.
The associations seem to be set up correctly as I can show them in my views and create them via the console. Where I have a problem is in letting the Miniatures NEW and EDIT views create and update to the Productions table.
In the console the command #miniature.productions.create(manufacturer_id: 1) works, which leads me to believe I should be able to do the same in a form.
I THINK my problem is always in the Miniatures Controller and specifically the CREATE function. I have tried out a ton of other peoples solutions there and none have done the trick. It is also possible that my field_for stuff in my form is wrong but that seems less fiddly.
I've been stuck on this for days and while there are other things I could work on, if this association isn't possible then I'd need to rethink my entire application.
The form now creates a line in the Productions table but doesn't include the all important manufacturer_id.
Any help VERY much appreciated.
My New Miniature form
<% provide(:title, 'Add miniature') %>
<h1>Add a miniature</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#miniature) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :production do |production_fields| %>
<%= production_fields.label :manufacturer_id, "Manufacturer" %>
<%= production_fields.select :manufacturer_id, options_from_collection_for_select(Manufacturer.all, :id, :name) %>
<% end %>
<%= f.label :release_date %>
<%= f.date_select :release_date, :start_year => Date.current.year, :end_year => 1970, :include_blank => true %>
<%= f.submit "Add miniature", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
Miniatures controller
class MiniaturesController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :edit, :update]
before_action :admin_user, only: :destroy
def productions
#production = #miniature.productions
end
def show
#miniature = Miniature.find(params[:id])
end
def new
#miniature = Miniature.new
end
def edit
#miniature = Miniature.find(params[:id])
end
def update
#miniature = Miniature.find(params[:id])
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
def index
#miniatures = Miniature.paginate(page: params[:page])
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
#production = #miniature.productions.create
redirect_to #miniature
else
render 'new'
end
end
def destroy
Miniature.find(params[:id]).destroy
flash[:success] = "Miniature destroyed."
redirect_to miniatures_url
end
private
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, :production, :production_attributes)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
Miniature model
class Miniature < ActiveRecord::Base
has_many :productions, dependent: :destroy
has_many :manufacturers, :through => :productions
accepts_nested_attributes_for :productions
validates :name, presence: true, length: { maximum: 50 }
validates :material, presence: true
validates :scale, presence: true
validates_date :release_date, :allow_blank => true
def name=(s)
super s.titleize
end
end
Production model
class Production < ActiveRecord::Base
belongs_to :miniature
belongs_to :manufacturer
end
Manufacturer model
class Manufacturer < ActiveRecord::Base
has_many :productions
has_many :miniatures, :through => :productions
validates :name, presence: true, length: { maximum: 50 }
accepts_nested_attributes_for :productions
end
Instead of calling:
#production = #miniature.productions.create
Try Rails' "build" method:
def new
#miniature = Miniature.new(miniature_params)
#miniature.productions.build
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
redirect_to #miniature
else
render 'new'
end
end
Using the build method uses ActiveRecord's Autosave Association functionality.
See http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
You also need to update your params method, e.g.
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, productions_attributes: [:manufacturer_id])
end
Also your fields_for should be plural (I think)...
<%= f.fields_for :productions do |production_fields| %>

Active record wont save for some reason

I have an model called assignment, which represents a rich join between users and questions in a multiple choice quiz app. when a user answers a question, the response is recorded in the following update method in the assignments controller
def edit
#assignment = Assignment.find_by_id(params[:id])
#user = #assignment.user
#question = #assignment.question
puts "user #{#user} question #{#question} assignment #{#assignment}"
end
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.save
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
puts #assignment.save
puts "could not save"
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end
I have a before save called grade that gets called. Here is the model:
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response
before_save :grade
def grade
self.correct = (response == self.question.solution) unless response == nil
end
end
So the first time I submit a response, the save works perfectly and it redirects me accordingly. after that, if i try to edit the question again and resubmit the form, the save fails.
Can anyone think of a reason this might be occurring?
In addition I know there is an error in the second redirect, so if someone could correct my usage that would be a bonus help
EDIT Here is the Edit erb, in case someone can spot something wrong here. I also added the edit method in the controller above.
<div class="admin-display">
<%if #admin%>
<p>
You may edit the user's response below or change the question to override whether the question is marked correct.
</p>
<%end%>
</div>
<div class="question body">
<%= #question.question %>
</div>
<div class="answer-choices">
<%= params[:user_id] + " " + params[:id] %>
<ol type="A">
<%=form_for(#assignment , :url => user_assignment_path(params[:user_id] , params[:id]) , :html => {:method => "put"}, :user_id => params[:user_id]) do |f|%>
<%[#question.answerA, #question.answerB ,#question.answerC ,#question.answerD].each do |choice|%>
<li>
<%= f.radio_button(:response, choice)%>
<%= choice %>
</li>
<%end%>
</ol>
<div class="form-buttons">
<%= submit_tag("Submit Response") %>
</div>
<%end%>
</div>
EDIT 2 I have just gone through the steps by hand in the rails console, without any issue, so there must be something strange going on.
It's probably due to the fact that your grade() callback method is returning a false, which will cancel the action all together (as stated in the documentation).
Hope this will work for you.
In Assignment model, add correct field as attr_accessible. First time as the response is nil, it is not executing the statement in before_save method, Hence your code will be
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response, :correct
before_save :grade
def grade
correct = (response == question.solution) unless response.nil?
end
end
and cotroller action could be
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.valid?
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end

Issues calling a method with multiple arguments

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.

Resources