How to pass the params to two different tables in rails - ruby-on-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.

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.

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.

Handling permalink user update when permalink is already taken Rails 3.2

I wanted some advice about how to handle to_param in regards to permalinks
Basically this is what happens.
Create a new company
The company :name is then parameterized and saved as a :permalink in the db
Updating an existing company enables you to change the :permalink
There are validations to ensure user updated :permalink is unique
The problem I'm having is occurring when updating the company's :permalink to something that already exists. The uniqueness validation works which is great, but it changes the params[:id] to the invalid permalink instead of reseting and using the existing params[:id]
When I try to edit the permalink to something else I get a flash validation error of "Name already taken" because it thinks I'm editing the company of the already existing :permalink (company). The URL reflects the change in permalink since my companies_controller.rb is using #company = Company.find_by_permalink[:id])
I wanted to know the best way to handle this issue?
class Companies < ActiveRecord::Base
before_create :set_permalink
before_update :update_permalink
attr_accessible :name, :permalink
validates :name, :permalink, uniqueness: { message: 'already taken' }
def to_param
permalink
end
private
def set_permalink_url
self.permalink = name.parameterize
end
def update_permalink_url
self.permalink = permalink.parameterize
end
end
Apologies if I'm not making too much sense.
Thanks in advance.
you could try to handle this with an after_rollback callback.
after_rollback :restore_permalink
def restore_permalink
self.permalink = permalink_was if permalink_changed?
end
here's how it works : every update / destroy in Rails is wrapped in a transaction. If the save fails, the transaction rollbacks and triggers the callback.
The callback then restores the old value (permalink_was) if it was changed since the record has been loaded.
See ActiveModel::Dirty and ActiveRecord::Transactions for more info.
EDIT
On the other hand, there may be another solution (untested) - just define your accessor like this :
def permalink=( value )
permalink_will_change! unless #permalink == value
#permalink = value
end
This way, the permalink will not be marked as dirty if the new value is identical to the old one, and so AR will not try to update the column.
Explanation:
i don't know on which version of rails it was implemented (it is relatively recent), but here's how "dirtyness" works :
your "standard" (automagically generated) attribute setters basicly call
#{your_attribute}_will_change! before setting the associated
instance variable (even if you set the exact same value than before)
when you call save, ActiveRecords looks for attributes that have changed ("dirty") and builds the SQL UPDATE query using ONLY these attributes (for performance reasons, mostly)
so if you want to avoid your permalink to appear in the query when it is unchanged, i think you have to override the standard setter - or avoid mass-assignment and only set permalink if it has changed

Ruby on Rails Validation Question Inside Controller

Let's say I have a form_tag in a view gathering a view user input . With this input that I get through the params hash, I want to verify some of htis information such as email phone name.
Can I just verify in the controller or is this a bad thing? I don't plan to save the thing I get to a db or anything, it's just getting put into an email and sent off, but I want to verify these things before it's sent.
Thank you.
EDIT:
Found this http://www.viddler.com/explore/rails3/videos/6/
class Test
include ActiveRecord::Validations
validates_presence_of :name, :email
attr_accessor :name, :email
end
You can use the model for whatever you need that is related to the object and don't need to save it. Keeping stuff like this in the model is desirable in order to keep controller tidy. Say you have a user model:
#controller
#user.new params[:user]
#user.check_valid
#user.save # is optional
#user.rb
def check_valid
!email.blank? and !phone.blank?
end

Resources