Rails call custom validation before .new or .create - ruby-on-rails

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.

Related

has_one nested association nullifies foreign key on edit route

I am building a nested form in ruby on rails.
The addition of a nested has_one association works fine. However, when I load the edit page, the foreign key company_id of the nested association is nullified.
I have tried update_only: true in accepts_nested_attributes_for and including :id in strong params as suggested in other similar answered questions on stackoverflow but nothing works for me.
Could anyone tell me what is actually causing the nested association to update and nullify its foreign key itself? My codes are as shown below. Thanks!
# company.rb
class Company < ApplicationRecord
has_one :mission
accepts_nested_attributes_for :mission, update_only: true
end
# mission.rb
class Mission < ApplicationRecord
belongs_to :company, optional: true
validates :description, presence: true, length: { maximum: 100 }
end
# companies_controller.rb
class CompaniesController < ApplicationController
def edit
#company = Company.find(params[:id])
#company.build_mission if #company.build_mission.nil?
end
def update
#company = Company.find(params[:id])
#company.assign_attributes(company_params)
if #company.valid?
#company.save
redirect_to companies_path
end
end
private
def company_params
params.require(:company).permit(mission_attributes: [:id, :description, :_destroy])
end
end
# edit.html.erb
<%= form_for #company, :url => company_path(#company), :html => {class: 'ui form', method: :put} do |f| %>
<%= f.fields_for :mission do |mission| %>
<div class="field">
<%= mission.label :mission %>
<%= mission.text_field :description %>
</div>
<% end %>
<%= f.button :submit => "", class: "ui button" %>
<% end %>
Hey I manage to solve the problem after a good sleep. Turns out i just have to play around with the if else condition at the companies controller level. The edit method should be amended to such:-
def edit
#company = Company.find(params[:id])
if #company.mission
else
#company.build_mission
end
end

Creating has_and_belongs_to_many objects within the same form in rails

I'm building a rails app, using devise for authentication and cancan for authorization. I have a form where you can create a "post" and each post has_and_belongs_to many "tags".
I want to create a system for creating tags similar to stack overflow, where the tags are simply inputed via a single text box and then converted to the appropriate tag objects on the server side. Initially I simply had a text box where I could type in a string and the string would be parsed as such in the controller
#post.tags << params[:post][:tags].split(' ').map{ |name| Tag.createOrReturnExisting name}
and that worked perfectly.. until I added cancan authorization, which required me to add
def post_params
params.require(:post).permit(:title,:description,:tags,:content,:types_id)
end
to my controller , which now causes the following error to be thrown upon trying to create a Post undefined method each for "Tag1 Tag2 Tag3\r\n":String I'm assuming this is because its trying to treat the string from the textbox like an array of tags before I've had a chance to format it.
So my question is, how must I format my controller, model, or view to be able to parse the string before it gets to the post_params method?
here's my models, view, and controller
Tag Model
class Tag < ActiveRecord::Base
has_and_belongs_to_many :post, join_table: 'tag_posts'
def self.createOrReturnExisting title
if Tag.any? {|tag| tag.title == title}
logger.debug "Tag: #{title} already exists"
Tag.find_by title: title
else
logger.debug "Tag: #{title} created"
Tag.new title: title
end
end
end
Post Model
class Post < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :tags, join_table: 'tags_posts'
has_one :type
validates :title, presence: true, length: { maximum: 50 }
validates :description, presence: true, length: { maximum: 255 }
validates :types_id, presence: true, length: { maximum: 255 }
end
new.html.erb
<h1>Post#new</h1>
<p>Find me in app/views/post/new.html.erb</p>
<%= form_for #post do |f| %>
Title: <%= f.text_field :title %>
Description: <%= f.text_area :description %>
Type: <%= f.collection_select( :types_id, Type.all, :id, :title ) %>
Content: <%= f.text_area :content%>
Tags: <%= f.text_area :tags%>
<%= f.submit %>
<% end %>
PostController
class PostsController < ApplicationController
load_and_authorize_resource
def miniList
render 'miniList'
end
def create
#post = Post.new
#post.title = params[:post][:title]
#post.description = params[:post][:description]
#post.content = params[:post][:content]
#tagStrings = params[:post][:tags].split(' ')
puts #tagStrings
#tagStrings.map do |name|
#tags << Tag.createOrReturnExisting name
end
#post.tags = #tags
#post.types_id = params[:post][:types_id]
if #post.save!
flash[:success] = "Post Saved Successfully"
else
flash[:error] = "Post not saved"
end
current_user.posts << #post
redirect_to :root
end
def new
#post = Post.new
render 'new'
end
def edit
end
def update
end
def delete
end
private
def post_params
params.require(:post).permit(:title,:description,:tags,:content,:types_id)
end
end
I figured it out, what I needed to do was change load_and_authorize_resource to authorize_resource since I don't want cancan messing with my parameters, this will just have it check authorization for my controller actions, and then leave the rest alone. I still wish there was a more intuitive way to do it, but this accomplishes what I need

Nested attributes validation on updating

I have two model Product and ProductBoxing, product has many product_boxings.
product.rb
class Product < ActiveRecord::Base
has_many :product_boxings
accepts_nested_attributes_for :product_boxings
validates :name, presence: { presence: true, message: 'please give a name' }, on: :update
end
product_boxing.rb
class ProductBoxing < ActiveRecord::Base
belongs_to :product
validates :quantity, presence: { presence: true, message: 'please fill in quantity' }, on: :update
end
_form.html.erb
<%= form_for(#product, html: {class: "form-horizontal", role: "form", multipart: true}) do |f| %>
<%= f.text_field :name%>
<%= f.fields_for :product_boxings do |g| %>
<%= g.text_field :quantity %>
<% end %>
<% end %>
For some reasons, I create both product and product_boxing without validation first. After that, I want to validate both on updating. The validation works for Product, but not for ProductBoxing.
Are there any problem in my code? or it is a rails issue?
BTW, I remove validation option on: :update and validate both on creating, the validations work for both.
update
At first, user will ran the follow code
def new
product = Product.new
p_b= product.product_boxings.build()
product.save!
redirect_to edit_product_path(product)
end
then
def edit
end
and post form
def update
#product.update(product_params)
unless #product.errors.any?
redirect_to products_url
else
render 'edit'
end
end
other info
def product_params
params.require(:product).permit(:name, product_boxings_attributes:[:id, :quantity] )
end
You should ensure the validation of your associated model:
class Product < ActiveRecord::Base
has_many :product_boxings
validates_associated :product_boxings
# ...
end
http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_associated
TEST THE MODELS WITH THE CONSOLE
> p = Product.new
> p.valid?
> p.errors
> p.product_boxings.each { |pb| pb.valid?; pb.errors }
If you want to see this product_boxings messages in the error list, you should create a custom validation.
validate :associated_product_boxings
def associated_product_boxings
product_boxings.each do |product_boxing|
errors.add (...) unless product_boxing.valid?
end
end

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| %>

Nested form not working to save client_id and user_id

I have Users, Users have many Clients and Contacts. Clients also have many Contacts (a Contact belongs to both Users and Clients).
In my client view, I want to create a new Client and on the same form allow the user to create the first Contact for that client. Initially, I thought using nested attributes would do the trick but I'm running into some issues. When I go to save #client in clients_controller#create, I can't save because user_id can't be blank and client_id can't be blank for the Contact. Here's what I have so far:
clients controller index (where the new client form is located):
def index
#clients = current_user.clients
#client = Client.new
#contact = #client.contacts.build
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
and the create method:
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
and the form:
= form_for(#client) do |f|
= f.fields_for(:contacts) do |contact|
but when I go to save it requires a client_id and user_id...but I can't really set those using the nested attributes. how can I accomplish this? is there a better way of doing this? here's my params:
{"name"=>"asdf", "contacts_attributes"=>{"0"=>{"name"=>"asdf", "email"=>"asdf#gmail.com"}}}
I just tried adding the missing values directly into the contacts_attributes but since #client hasn't been saved yet, I can't assign the client.id to contact:
params[:client][:contacts_attributes]["0"].merge!(:user_id => current_user.id)
params[:client][:contacts_attributes]["0"].merge!(:client_id => #client.id)
even when user_id is set...it still says user is missing.
Did you add accepts_nested_attributes_for to your model? You need something like this:
class Client < ActiveRecord::Base
has_many :contacts
accepts_nested_attributes_for :contacts
end
Also, you should be using build in your create action:
#client = current_user.clients.build(params[:client])
Here's my setup that worked for your example:
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :clients_attributes
has_many :clients
accepts_nested_attributes_for :clients
end
app/models/client.rb:
class Client < ActiveRecord::Base
attr_accessible :company, :contacts_attributes
belongs_to :user
has_many :contacts
accepts_nested_attributes_for :contacts
end
app/models/contact.rb:
class Contact < ActiveRecord::Base
attr_accessible :email
belongs_to :client
end
app/controllers/users_controller.rb:
class UsersController < ApplicationController
# ...
def new
#user = User.new
#client = #user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The actual form:
<% # app/views/users/new.erb %>
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.fields_for :clients do |client_form| %>
<h5>Client</h5>
<%= client_form.label :company, "Company name" %>
<%= client_form.text_field :company %>
<div class="field">
<h5>Contact</h5>
<%= client_form.fields_for :contacts do |contact_form| %>
<%= contact_form.label :email %>
<%= contact_form.text_field :email %>
<% end %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form looks like this:
Here's how params sent by forms look like:
{
"utf8"=>"✓",
"authenticity_token" => "bG6Lv62ekvK7OS86Hg/RMQe9S0sUw0iB4PCiYnsnsE8=",
"user" => {
"name" => "My new user",
"clients_attributes" => {
"0" => {
"company" => "Client's Company Name LLC",
"contacts_attributes" => {
"0" => {
"email" => "emailbox#client.com"
}
}
}
}
},
"commit" => "Create User"
}
Update
To enable adding more companies/contacts afterwards, change the UsersController#edit action to this:
class UsersController < ApplicationController
# ...
def edit
#client = current_user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The following validations may be causing this problem since the parent id has not assigned to a child object at the time the validations are run for it. The workaround to this is to either remove these validations or set them to run on update only. This way you lose out on these validations for the create method but it works.
# client.rb
validates :user, presence: true
# contact.rb
validates :client, presence: true

Resources