Rails 5 Not able to store in database - ruby-on-rails

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.

Related

How to stop create in rails 5 without rolling back changes?

I need to check if a similar record exist in database before save, if true then update the existing record without saving, else create a new one
In rails 5:
returning false in a hook method doesn't halt callbacks and "throw :abort" is used instead.
the problem is using "throw :abort" rolls back any changes made in the before_save callback.
what I am trying to do is to check for a similar recored in "before_save" and if a similar record exist I need to update the current record and stop saving the new one.
I used
before_save :check
def check
if (similar record exist..)
update current...
return false <==========
end
true
end
but this is not working any more in Rails 5 so returning false doesn't stop it from saving the new record too.
and I tried
before_save :check
def check
if (exist..)
update current...
throw :abort <========
end
true
end
this stops saving current record to db but it perform "rollback" so the updated recored is missed !!
how can I do that ?
I think this is one possible way. This example if with a Product model looking for same name.
before_create :rollback_if_similar_exists
after_rollback :update_existing_record
def rollback_if_similar_exists
throw :abort if Product.exists? name: self.name
end
def update_existing_record
# do here what you need
puts name
puts "find the existing record"
puts "update data"
end
Here is a slightly different approach you could take:
Instead of using before_save, create your own validation and use assign_attributes instead of update or create since assign_attributes won't actually write to the database. You could then call the valid? function on your record to execute your validations. If you get a duplicate record error from the validation you defined, then have your code handle updating the existing record in the logic of your error handling.
Something like this in your controller:
#record.assign_attributes(my_parameter: params[:my_parameter])
if #record.valid?
#record.save
else
puts #record.errors.messages.inspect
#update your existing record instead.
end
Then in your model:
validate :my_validation
def my_validation
if record_already_exists
return errors.add :base, "Your custom error message here."
end
end
I'd recommend using #find_or_initialize_by or #find_or_create_by to instantiate your instances. Instead of placing record swapping code inside a callback. This means you'll do something like this (example controller create):
class Post < ApplicationController
def create
#post = Post.find_or_initialize_by(title: param[:title])
if #post.update(post_params)
redirect_to #post
else
render :new
end
end
end
Pair this with a validation that doesn't allow you to create double records with similar attributes and you're set.
create_table :posts do |t|
t.string :title, null: false
t.text :body
end
add_index :posts, :title, unique: true
class Post < ApplicationRecord
validates :title, presence: true, uniqueness: true
end
I don't recommend the following code, but you could set the id of your instance to match the record with similar data. However you'll have to bypass persistence (keeps track of new and persistent records) and dirty (keeps track of attribute changes). Otherwise you'll create a new record or update the current id instead of the similar record id:
class Post < ApplicationRecord
before_save :set_similar_id
private
def set_similar_id
similar_record = Post.find_by(title: title)
return unless similar_record
#attributes['id'].instance_variable_set :#value, similar_record.id
#new_record = false
end
end
Keep in mind that only changes are submitted to the database when creating a new record. For new records these are only the attributes of which the attributes are set, attributes with value nil are not submitted and will keep their old value.
For existing records theses are the attributes that are not the same as there older variant and the rule old_value != new_value (not actual variable names) applies.

Rails nested form - refactor create action | cocoon gem

Everything is working fine but I want to change the code in create action to something like in update action. Right now, in the create action I am looping through all the values and saving them, but want to do it in a single line.
I have a College model.
class College< ActiveRecord::Base
has_many :staffs, dependent: :destroy
accepts_nested_attributes_for :staffs, reject_if: :all_blank, allow_destroy: true
end
And this is my Staff.rb
class Staff < ActiveRecord::Base
belongs_to :college
end
And these are my Staffs controller create and update actions
def create
#college= College.find(params[:college][:id_college_profile]
)
staff_params = params[:college][:staffs_attributes].values
staff_params.each do |staff_param|
#staff = #college.staffs.new
#staff.name = staff_param[:name]
#staff.designation = staff_param[:designation]
#staff.experience = staff_param[:experience]
#staff.specialization = staff_param[:specialization]
#staff.save
end
redirect_to dashboard_path(id: #college.id), notice: "Successfully added Staff."
end
def update
#college= College.find(params[:college][:id_college]
)
#college.update_attributes(staff_parameters)
redirect_to root_path
end
These are strong parameters
def staff_parameters
params.require(:college).permit(:id, staffs_attributes: [:id, :name, :specialization, :experience, :designation, :_destroy])
end
Is there a way to save all of staffs in create action, without looping through all the values, but save all of them with a single line of code as in update action?
I have tried this in the StaffsController create action
def create
#college= College.find(params[:college][:id_college]
)
#staff= #college.staffs.new(staff_parameters)
#staff.save
redirect_to dashboard_path(id: #college.id), notice: "Successfully added Staffs."
end
But it threw this error
unknown attribute 'staffs_attributes' for Staff.
Can someone kindly help me with this issue?
This is a CollegesController so I am assuming the create action also creates the new college?
So in that case your create action should simply be something like:
def create
#college = College.new(staff_parameters)
if #college.save
# succesfully created
else
# there was a validation error
end
end
Note that in general we would use college_parameters because the root element is college and that you not only edit the nested staff, but also possibly attributes from college.
If the college always already exists (because you are doing a find), it is a bit confusing to me what the difference is between create and update and why not always render the edit action in that case?
I have a demo-project show-casing cocoon and nested attributes.
You can do this many ways. The "staff_parameters" method threw an error because you are calling it on class Staff in the create action and on the college class for the update action. Simplest thing to do what you want is to copy the staff parameters method strong parameters and duplicate it. Name this second method create_staff and change the "params.require(:college)" part to "params.require(:staff)" and leave the rest the same. Then in your create action you can do "college.staff(create_staff)". Im on my phone so the formatting isnt good lol i put the code in quotes.

Using accepts_nested_attributes_for outside of proper restful routes

I am building a setup wizard for a model called Project. This model has a lot of associated information, which includes a number of nested models.
After some research and a fair bit of trial and error, I decided to manage the setup process in a SetupController, using the :id parameter to track which step I'm on, resulting in a path pattern like so: projects/:project_id/setup/:id/edit (based on this blog)
Here are the relevant bits:
Project Model
class Project < ActiveRecord::Base
has_many :ratings
accepts_nested_attributes_for :ratings, allow_destroy: true, reject_if: -> x { x[:value].blank? }
end
Rating Model
class Rating < ActiveRecord::Base
# has a null: false constraint on value
belongs_to :project
end
Setup Controller
ProjectSetupController < ApplicationController
STEPS = %w(step_1 step_2 step_3)
layout 'setups'
def edit
#project.ratings.build
render step
end
def update
if #project.update_attributes(project_params)
if next_step && params[:button].downcase.include?('continue')
redirect_to edit_project_setup_path(#project, next_step), flash: {success: "Updated project"}
else
redirect_to project_path(#project)
end
else
flash.now[:error] = "Please complete all required fields"
render step
end
end
private
def step
STEPS.find {|s| s == params[:id].to_s.downcase}
end
def current_step_index
STEPS.index(step)
end
def next_step
STEPS[current_step_index+1]
end
def project_params
params.require(:project).permit(:name, ratings_attributes: [:id, :value, :_destroy])
end
end
And this is all well and good, except when it comes to nested attributes. A Project accepts_nested_attributes_for Ratings, but rejects any ratings with blank values. I want the user to be able to submit a form with blank values because multiple rating fields can be added dynamically to the project form and there will always be an empty new field, I just don't want any record without a value to be saved. However, something gets muddled when using the :id parameter as something other than the id of the parent model, and these records are not discarded when the form is submitted. Instead, they hit the Rating database validation for presence of value and an error is thrown.
Form
= simple_form_for #project, url: project_setup_path(#project, params[:id]), as: :project, html: {id: 'customization-form'} do |f|
- #project.ratings.each do |rating|
.rating-wrapper{class: rating.new_record? && "new"}
= f.fields_for :ratings, rating do |ff|
= ff.input_field :value, placeholder: "Enter New Rating"
= button_tag(type: 'submit') do
Update
If I mock a submission with the params[:id] as the id of the project I'm submitting the form for, then everything works as expected (of course this results in a redirect error as the project id is not a valid step), so I feel like there must be some way to point the attributes to the correct id, alas, this magic is beyond me.
Current possible workarounds:
I can submit the form to the regular project controller action with a
button parameter that will redirect the user back into the setup
process
I could remove the empty value fields from the DOM via javascript on
submission
If I remove the Rating validations, I can submit the form as
is, and all blank ratings will be saved, but I could delete them in a
callback
Currently I'm employing the first workaround, but is there a more Rails-y solution that allows me to keep this process within the setup controller, without removing database validations or using javascript? The blog article I modeled my wizard after suggested sub-models for handling interim validations - I don't think that's exactly what I'm looking for here, but maybe there's a way I could leverage something like that?

Rails 4 update_attributes with nested attributes not saving

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.

ActiveModel::MassAssignmentSecurity::Error in SubjectsController#create

Can't mass-assign protected attributes: created_at(2i), created_at(3i), created_at(1i), created_at(4i), created_at(5i)
my code is below:
def new
#subject = Subject.new(:name => 'default')
#subject_count = Subject.count + 1
end
def create
# Instantiate a new object using form parameters
#subject = Subject.new(params[:subject])
# Save the object
if #subject.save
# If save succeeds, redirect to the list action
flash[:notice] = "Subject created."
redirect_to(:action => 'list')
else
# If save fails, redisplay the form so user can fix problems
#subject_count = Subject.count + 1
render('new')
what is the problem?
You can try this,
In your subject model add all attributes you used in your form such as,
class Subject < ActiveRecord::Base
attr_accessible :name, :created_at
...
...
end
Either set config.active_record.whitelist_attributes in application to false in application.rb (not recommended) or whitelist attributes in your model using attr_accessible as such
attr_accessible :name, :etc...
Make sure to whitelist the attributes sent via the controller in the Subject model as explained by Alex. You Subject model should look like:
class Subject < ActiveRecord::Base
attr_accessible :created_at
end
Why? Rails requires this recently (after a security problem on Github) to make sure a malicious user doesn't send unwanted attributes from the frontend. Suppose you have an :admin boolean field on a User class. Without the attr_accessible someone could add a new field in the form, something like <input type="checkbox" name="user[admin]" value="true" checked>, which would give him admin powers to your application.
Sidenote... you don't need to set the :created_at date from the HTML form, ActiveRecord will manage the :created_at and :updated_at automatically for you (unless you want to set it manually for some reason off course).

Resources