Rails 4 update_attributes with nested attributes not saving - ruby-on-rails

I am trying to break up the registration process of my app into nice bite-size chunks.
So I'm posting forms via Ajax and just trying to update some of the models attributes that were available at that step of that process, basically meaning that they won't be valid at each save point.
As a result, I have been using update_attribute which works fine. However, one of my attributes is a has_many association and I'm struggling to get this working.
I have Channelmodel with has_many :channel_tags, and also accepts_nested_attributes_for :channel_tags. Saving and updating work fine when I use the update method but I cannot get it working with update_attribute or update_attributes.
As far as I can tell, I need to use update_attributes. I wanted to do something like:
#channel.update_attributes(channel_tags_attributes: params[:channel][:channel_tags_attributes])
But this doesn't create the new channel_tags. I have also tried with:
#channel.update_attributes(tag_params)
and:
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
But again, it just doesn't seem to do anything.
When checking from the console, it appears that all of it's happening because that it's loading the Channel for the database and then the category.
Am I doing something wrong, or is there a better way of doing this?

Try to change the name to permitted params method:-
def channel_params
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
end
and user this method in update attributes:-
def update
#channel = Channel.find(params[:id])
if #channel.update_attributes(channel_params)
# add your code here
end
end

As you are trying to do it with nested attributes in rails 4 then your code should look like,
In tag model
has_many :channel_tags
accepts_nested_attributes_for :channel_tags, allow_destroy: true
Controller should look like
def update
#tag = Tag.find(params[:id])
puts "==== #{tag_params.inspect} ===="
puts "==== #{tag_params[:channel_tags].inspect} ===="
if #tag.update!(tag_params)
redirect_path
end
end
private
def tag_params
params.require(:tag).permit(:name ,channel_tags_attributes: [:id, :channel_id, :tag_id, :_destroy])
end
While updating attributes please check the server logs as I have inspect the params which you try to update for the tag attribs.
.update_attributes will only when you want particular attributes. .update will use HASH in params which we are defining as strong params.

Related

Rails 5 Not able to store in database

I am not able to store the record in the database following is my code
Form Parameters
Controller
def create
#sales_dailystatus_info = SalesDailystatusInfo.new(sales_dailystatus_info_params)
#sales_dailystatus_info.user_id = current_user.id
#project = #sales_dailystatus_info.project
respond_to do |format|
if #sales_dailystatus_info.save
byebug
format.js{}
format.html{redirect_to dashboard_project_path(#project)}
else
format.js{}
format.html{render nothing: true}
end
end
end
def sales_dailystatus_info_params
params.require(:sales_dailystatus_info).permit(:user_id, :project_id, :sales_task_id,
:task_time, :description)
end
Model
class SalesDailystatusInfo < ActiveRecord::Base
belongs_to :project
belongs_to :user, optional: true
belongs_to :sales_task
validates :user_id, :sales_tasks_id, :task_time, presence: true
end
You can see in the screenshot I got rollback while save.
Please help me.
Edit:
I have made the change, Now I am iterating the params and remove strong parameters. Following is my code
def create
params[:sales_dailystatus_info].values.each do |sales_dailystatus_info|
#sales_dailystatus_info = SalesDailystatusInfo.create(
project_id: sales_dailystatus_info[:project_id],
sales_tasks_id: sales_dailystatus_info[:sales_task_id],
task_time: sales_dailystatus_info[:task_time],
description: sales_dailystatus_info[:description],
user_id: current_user.id
);
byebug
end
respond_to do |format|
format.js{}
format.html{render nothing: true}
end
end
still not able to save it. Give me error Sales task must exist.
Looking at your logs, it looks like your are submitting two SalesDailyStatusInfo:
{... 'sales_daily_status_info" => { "0" => {"project_id" => ...}, "1" => { "project_id" => ... } } }
You don't allow those keys in your params sanitizer, hence the Unpermitted parameters: :0, :1. The result is that your don't whitelist any params you submit and the params hash is empty, your model validations fail.
In order for this to work you either need to send only one project at a time or loop through your params to create both SalesDailyStatusInfo.
Add the frontend form code to your question if you need further help.
Hope it helps !
Looks like your record is not valid. You validate presence of the user_id but it's not sent. Instead of rendering nothing try to render #sales_dailystatus_info.errors.messages
Why do you validate :user_id and put optional: true at the same time? Also in your model you have validation of :sales_task_id (belongs_to association default validation on Ruby on Rails version >= 5.0) and :sales_tasks_id. And in your controller I do see :sales_tasks_id key but I do not see :sales_task_id, that is why you receive Sales task must exist error. Remove unnecessary validations of :user_id and :sales_task_id, return back strong parameters and everything will be fine
Since you are assigning a project_id and a sales_task_id, those records must be created before you can create a sales_dailystatus_info record. The error you are getting is saying you don’t have a sales_task created.
I solved it by changing the name of the field.
I change the field sales_tasks_id in sales_daily status_info to sales_task_id.
I need a singular foreign key name instead of plural.

How to pass the params to two different tables in rails

As a newbie I started to do API POC. I have a situation as explained below:
I have seekerController which has create method.I want that when a Post request makes then few parameters has to go seeker table and few needs to go profile table(This table also have the seekerID column). I want to do this with in Transaction commit. So after reading I started doing below:-
ActiveRecord::Base.transaction do
seeker = Seeker.new(seeker_params)
seeker.save!
params[:seeker_id] = seeker[:id]
seekerprofile = SeekerProfile.new(seekerprofile_params)
seekerprofile.save!
end
render json: {status: 'success', message: 'Request is processed successully', data:seeker},status: :created;
I have below definition:(I have doubt i the below way is correct)
def seeker_params
params.require(:seeker).permit(:username, :alias, :mobile_number, :country_code, :email_address, :description, :status)
end
def seekerprofile_params
params.require(:seeker_profile).permit(:seeker_id, :first_name, :middle_name, :last_name, :date_of_birth, :pincode, :building_name, :address, :email_address, :description, :status)
end
Let me put my question straight forward here:-
I have post body request parameter like below:
{
"username" : "TestName12",
"alias" : "TestAlia12",
#above should go to seeker table
"first_name":"xyz",
"Last_Name":"abc"
#above should go above Seekerprofile table. seekerprofile has seekerid also.
}
My Model is below:-
> class SeekerProfile < ApplicationRecord
>
> belongs_to :seeker end
I have tried what i have posted in start code , but i am getting the error as seekerprofile_params is empty. So I am sure my approach is wrong.
Can anyone please provide the sample code , how to do that? I am java guy , so fresher for ruby.
With the limited information that is given, it seems as though the problem could be related to the seeker_id field being blank in the result of seekerprofile_params. Basically, we're setting params[:seeker_id] as params[:seeker_id] = seeker[:id] after saving Seeker. But while creating params for creating SeekerProfile, we use seekerprofile_params which looks for seeker_id in params[:seeker_profile][:seeker_id] since we use params.require(:seeker_profile) before permitting seeker_id. Since SeekerProfile does not get a seeker_id, it may not get saved depending on how the model is setup.
However, in case you're trying to create both, a Seeker as well as a SeekerProfile, you may want to check out nested attributes in Rails.
Edit after receiving more inputs:
Considering that the API contract cannot change and needs to be maintained, the following approach could be used to create a seeker and a seeker_profile:
1) We could change the model Seeker to accept nested attributes for SeekerProfile as follows:
# app/models/seeker.rb
has_many :seeker_profiles # As mentioned in the question comments
accepts_nested_attributes_for :seeker_profiles
2) The controller code could then be changed as follows:
# app/controllers/seeker_controller.rb
def create
seeker = Seeker.new(creation_params)
seeker.save!
render json: {status: 'success', message: 'Request is processed successully', data:seeker},status: :created
end
private
def creation_params
params.permit(:username, :alias).merge(seeker_profiles_attributes: [seeker_profile_creation_params])
end
def seeker_profile_creation_params
params.permit(:first_name, :last_name)
end
What happens here is basically we allow the seeker model to accept attributes for seeker_profiles during creation. These attributes are accepted by the model using the seeker_profiles_attributes attribute writer. Since the relationship is a has_many relationship, seeker_profiles_attributes accepts an array of objects, where each hash object represents one seeker_profile child to be created.
In the code mentioned above, I've assumed that only one seeker_profile is to be created. In case your API changes and wants to accept multiple profiles during creation, I would leave that upto you to figure out, with the assurance that you could get back in the comments in case you're stuck.
Another thing to note is that the ActiveRecord::Base.transaction block is not required since failure in any of the objects being created would rollback the entire transaction anyway.

Validate Associated Object Presence Before Create

I've been following the Getting Started rails tutorial and am now trying some custom functionality.
I have 2 models, Person and Hangout. A Person can have many Hangouts. When creating a Hangout, a Person has to be selected and associated with the new Hangout. I'm running into issues however when I call my create action. This fires before the validate_presence_of for person.
Am I going about this the wrong way? Seems like I shouldn't have to create a custom before_create validation to make sure that a Hangout was created with a Person.
#hangout_controller
def create
#person = Person.find(params[:hangout][:person_id])
#hangout = #person.hangouts.create(hangout_params)
#hangout.save
redirect_to hangouts_path(#hangout)
end
#hangout.rb
class Hangout < ActiveRecord::Base
belongs_to :person
validates_presence_of :person
end
#person.rb
class Person < ActiveRecord::Base
has_many :hangouts
validates :first_name, presence: true
validates :met_location, presence: true
validates :last_contacted, presence: true
def full_name
"#{first_name} #{last_name}"
end
end
Create action fires before the validate_presence_of for person
I think you are confused about rails MVC. Your form contains a url and when you submit your form your form params are send to your controller action according to the routes you have defined in routes.rb Your controller action, in this case create action, interacts with model this is very it checks for your validations and if all the validations are passed your object is saved in databse so even though in your app the control is first passed to your controller but your object is saved only once if all the validations are passed.
Now lets comeback to your code. There are couple of things you are doing wrong
a. You don't need to associate your person separately:
In your create action you have this line:
#person = Person.find(params[:hangout][:person_id])
You don't need to do this because your person_id is already coming from your form and it'll automatically associate your hangout with person.
b. You are calling create method instead of build:
When you call .association.create method it does two things for you it first initialize your object, in your case your hangout and if all the validations are passed it saves it. If all the validations are not passed it simply rollback your query.
If you'll use .association.build it'll only initialize your object with the params coming from your form
c. Validation errors won't show:
As explained above, since you are calling create method instead of build your validation error won't show up.
Fix
Your create method should look like this:
def create
#hangout = Hangout.new(hangout_params) # since your person_id is coming from form it'll automatically associate your new hangout with person
if #hangout.save
redirect_to hangouts_path(#hangout)
else
render "new" # this will show up validation errors in your form if your hangout is not saved in database
end
end
private
def hangout_params
params.require(:hangout).permit(:person_id, :other_attributes)
end
You are confused with the controller and model responsibilities.
Let me try to explain what I think is confusing you:
First try this in your rails console:
Hangout.create
It shouldn't let you because you are not passing a Person object to the create method. So, we confirm that the validation is working fine. That validation means that before creating a Hangout, make sure that there is a person attribute. All this is at the model level, nothing about controllers yet!
Let's go to the controllers part. When the create action of the controller 'is fired', that controller doesn't know what you are trying to do at all. It doesn't run any validations. It is just an action, that if you want, can call the Hangout model to create one of those.
I believe that when you say 'it fires' you are saying that the create action of the HangoutController is called first than the create method on the Hangout model. And that is completely fine. The validations run at the model level.
Nested Attributes
I think you'll be better using accepts_nested_attributes_for - we've achieved functionality you're seeking before by using validation on the nested model (although you'll be able to get away with using reject_if: :all_blank):
#app/models/person.rb
Class Person < ActiveRecord::Base
has_many :hangouts
accepts_nested_attributes_for :hangouts, reject_if: :all_blank
end
#app/models/hangout.rb
Class Hangout < ActiveRecord::Base
belongs_to :person
end
This will give you the ability to call the reject_if: :all_blank method -
Passing :all_blank instead of a Proc will create a proc that will
reject a record where all the attributes are blank excluding any value
for _destroy.
--
This means you'll be able to create the following:
#config/routes.rb
resources :people do
resources :hangouts # -> domain.com/people/:people_id/hangouts/new
end
#app/controllers/hangouts_controller.rb
Class HangoutsController < ApplicationController
def new
#person = Person.find params[:people_id]
#hangout = #person.hangouts.build
end
def create
#person = Person.find params[:people_id]
#person.update(hangout_attributes)
end
private
def hangout_attributes
params.require(:person).permit(hangouts_attributes: [:hangout, :attributes])
end
end
Although I've not tested the above, I believe this is the way you should handle it. This will basically save the Hangout associated object for a particular Person - allowing you to reject if the Hangout associated object is blank
The views would be as follows:
#app/views/hangouts/new.html.erb
<%= form_for [#person, #hangout] do |f| %>
<%= f.fields_for :hangouts do |h| %>
<%= h.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>

build automatically nested object

in my client_controller.rb
def edit
#client = Client.find(params[:id])
#client.build_address unless #client.address
...
end
...address is nested polymorphic attribute (1:1)
I don't like to call the build attribute in controller
my question:
is it good idea to automaticly build nested object if it isn't build yet ?
example:
class Client
has_one :address, :as => :addressable #polymorphic
#...
def address
super || build_address
end
end
question2:
is there better way to do that ?
Implemented like that in project form more than 6 weeks, still no problem at all,
so yeah looks like a good idea in my case, see question comments

Validate model for certain action

I need to validate a model only for a certain action (:create). I know this is not a good tactic, but i just need to do this in my case.
I tried using something like :
validate :check_gold, :if => :create
or
validate :check_gold, :on => :create
But i get errors. The problem is that i cannot have my custom check_gold validation execute on edit, but only on create (since checking gold has to be done, only when alliance is created, not edited).
Thanx for reading :)
I'm appending some actual code :
attr_accessor :required_gold, :has_alliance
validate :check_gold
validate :check_has_alliance
This is the Alliance model. :required_gold and :has_alliance are both set in the controller(they are virtual attributes, because i need info from the controller). Now, the actual validators are:
def check_gold
self.errors.add(:you_need, "100 gold to create your alliance!") if required_gold < GOLD_NEEDED_TO_CREATE_ALLIANCE
end
def check_has_alliance
self.errors.add(:you_already, "have an alliance and you cannot create another one !") if has_alliance == true
end
This works great for create, but i want to restrict it to create alone and not edit or the other actions of the scaffold.
All ActiveRecord validators have a :on option.
validates_numericality_of :value, :on => :create
Use the validate_on_create callback instead of validate:
validate_on_create :check_gold
validate_on_create :check_has_alliance
Edit:
If you use validates_each you can use the standard options available for a validator declaration.
validates_each :required_gold, :has_alliance, :on => :create do |r, attr, value|
r.check_gold if attr == :required_gold
r.check_has_alliance if attr == :has_alliance
end
Like Sam said, you need a before_create callback. Callbacks basically mean 'execute this method whenever this action is triggered'. (More about callbacks here : http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).
This is what you want in your model:
before_create :check_gold
# other methods go here
private # validations don't need to be called outside the model
def check_gold
# do your validation magic here
end
The above method is the simplest to do what you want, but FYI there's also a way to use a before_save callback to execute additional actions on creation:
before_save :check_gold_levels
# other methods
private
def check_gold_levels
initialize_gold_level if new? # this will be done only on creation (i.e. if this model's instance hasn't been persisted in the database yet)
verify_gold_level # this happens on every save
end
For more info on 'new?' see http://api.rubyonrails.org/classes/ActiveResource/Base.html#method-i-new%3F
You need to look into callbacks. Someone once told me this and I didn't understand what they meant. Just do a search for rails callbacks and you will get the picture.
In your model you need to do a callback. The callback you need is before_create and then before a object is created you will be able to do some logic for check for errors.
model.rb
before_create :check_gold_validation
def check_gold_validation
validate :check_gold
end
def check_gold
errors.add_to_base "Some Error" if self.some_condition?
end

Resources