RoR ActiveRecord save method - ruby-on-rails

Im new to Rails, im trying to execute the save method within an ActionController's Create method multiple times to insert multiple values
def create
#pin = Pin.new(params[:pin])
i = 1
while i < 10
if #pin.save
end
end
redirect_to #pin
end
This works but only inserts one record
there's no Contraints that enforces uniqueness of Records in my Database.
Please how do i correct this?

One AR objects maps to one row. You need to create new object for each row you want added.
Something like that:
10.times do
pin = Pin.new(params[:pin])
pin.save
end
or
10.times do
Pin.create(params[:pin]
end
create method creates an AR object and saves it in the database.
However, you cannot redirect to 10 objects.

you should extend your resource with create_multiple method and send params as array, see the details here

Related

flexible system to destroy each records in batch

client_skipped_day_controller.rb
class ClientSkippedDaysController < ApplicationController
before_action :check_client_on_exist, only: [:create]
def index
#client_skipped_days = ClientSkippedDay.order_by(params[:sort_by], params[:direction])
if params[:date].present?
#client_skipped_days = #client_skipped_days.where('skipped_at = ?', Date.parse(params[:date]))
end
render json: #client_skipped_days, status: :ok
end
def create
#client_skipped_days = ClientSkippedDay.create!(client_skipped_days_params)
render json: #client_skipped_days, status: :created
end
def destroy
end
private
def client_skipped_days_params
params.permit(client_skipped_days: %i[client_id skipped_at])[:client_skipped_days]
end
def check_client_on_exist
client_skipped_days_params.each do |day|
ClientSkippedDay.find_by(day)&.destroy
end
end
end
My code works if I try to delete only one record, like a :
Parameters: {"client_skipped_days"=>[{"client_id"=>533, "skipped_at"=>"2019-02-24"}], "client_skipped_day"=>{}}
But if I try to delete each hash in the array, it's didn't work :(
Parameters: {"client_skipped_days"=>[{"client_id"=>533, "skipped_at"=>"2019-02-24"}, {"client_id"=>512, "skipped_at"=>"2019-02-24"}], "client_skipped_day"=>{}}
Only one record will be deleted, but how to add the ability to delete all records? which coincide with the parameters that come from the controller?
And it must be a flexible system to remove if 1 hash in the array and immediately a collection of hashes in the array. Tell me how to do it.
Instead of looping over the params and finding each record one by one you could also consider using multiple #where queries combining them together with the use of #or and loop over the resulting records.
def client_skipped_days_params
params.permit(client_skipped_days: [:client_id, :skipped_at])
# removed `.values` ^
end
def check_client_on_exist
destroyed_records, undestroyed_records =
client_skipped_days_params
.fetch(:client_skipped_days, []) # get the array or use an empty array as default
.map(&ClientSkippedDay.method(:where)) # build individual queries
.reduce(ClientSkippedDay.none, :or) # stitch the queries together using #or
.partition(&:destroy) # call #destroy on each item in the collection, separating destroyed once from undestroyed once
end
In the above example the resulting destroyed records are present in the destroyed_records variable and the records that could not be destroyed are present in the undestroyed_records variable. If you don't care about the result you can leave this out. If you want to raise an exception if a record cannot be destroyed use #destroy! instead (call upon each collection item).
Alternatively you can destroy all records by calling #destroy_all (called upon the collection), but it will simply return an array of records without differentiating the destroyed records from the undestroyed records. This method will still instantiate the records and destroy them one by one with the advantage that callbacks are still triggered.
The faster option is calling #delete_all (called upon the collection). This will destroy all records with one single query. However records are not instantiated when destroyed, meaning that callbacks will not be triggered.
def check_client_on_exist
destroyed_record_count =
# ...
.reduce(ClientSkippedDay.none, :or)
.delete_all # delete all records with a single query (without instantiation)
end
references:
ActionController::Parameters#fetch
Array#map
ActiveRecord::QueryMethods#none
Enumerable#reduce
Enumerable#partition
You need to loop over your array instead of just taking the first value out of it. I don’t understand the params that you have, so I’m assuming that you want to do your find_by using the Hash of client_id and skipped_at.
Also, Ruby 2.3.0 introduced the safe navigation operator, which is what that &. is if you aren’t used to it. http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
Since find_by either returns an ActiveRecord object or nil, it’s a great time to use the safe navigation operator to shorten things up.
def client_skipped_days_params
params.permit(client_skipped_days: %i[client_id skipped_at])[:client_skipped_days]
end
def check_client_on_exist
client_skipped_days_params.each do |day|
ClientSkippedDay.find_by(day)&.destroy
end
end
Note, I’m not sure what your client_skipped_day Hash is. I assumed you’re making it possible to delete a single day, or delete in bulk. I would warn against having it do two things. Just make the client always send an array for this action and things will be easier for you. If you can do that, then you can make client_skipped_days required.
def client_skipped_days_params
params.require(:client_skipped_days).permit(%i[client_id skipped_at])
end
This will raise a 422 error to the client if they don’t provide the client_skipped_days key.
If this isn’t possible, then you’ll need to add an if to check_on_exist to make sure that client_skipped_days_params is not null (because they’re using client_skipped_day).

How to create a new joined table record without creating a new instance of either record. Rails

I have a three tables: User, fruits, user_fruits
There are set number of users and fruits in the world. I cannot create duplicate fruits. Say the fruits already exist in the database, and I want to use them, so I'm not creating new fruits.
Assume we're in a controller and all I want to do is create a new instance of user_fruits. I also want to delete the joined table instance in the destroy action but not the actual fruit itself. Is this the way to do it?
def create
user.user_fruits.create!(fruit: fruit)
end
def destroy
user.user_fruits.find_by(fruit: fruit).destroy!
end
private
attr_reader :fruit
def load_fruit
#fruit = Fruit.find_by(color: red, sweetness: 100)
end
I also want my destroy and create to raise an error if it fails.
If you just want to create a new instance then you can simply use new or build
def create
#new_fruit = user.fruits.new # or fruits.build
end
Update
New instance of user_fruits?
Then you should try something like..
user.user_fruits.new
def create
user.user_fruits.create!(fruit_id: #fruit.id)
end
def destroy
user.user_fruits.find_by_fruit_id(#fruit.id).destroy!
end
And if you want to use reader anyway then replace #fruit with fruit

How to implement controller in order to handle the creation of one or more than one record?

I am using Ruby on Rails 4.1. I have a "nested" model and in its controller I would like to make the RESTful create action to handle cases when one or more than one records are submitted. That is, my controller create action is:
def create
#nester = Nester.find(:nester_id)
#nesters_nested_objects = #nester.nested_objects.build(create_params)
if #nnesters_ested_objects.save
# ...
else
# ...
end
end
def create_params
params.require(:nesters_nested_object).permit(:attr_one, :attr_two, :attr_three)
end
I would like it to handle both cases when params contain data related to one object and when it contains data related to more than one object.
How can I make that? Should I implement a new controller action (maybe called create_multiple) or what? There is a common practice in order to handling these cases?
Well, if you insist on creating those records aside from their nest, I can propose to go with something like this (it better be a separate method really):
def create_multiple
#nest = Nester.find(params[:nester])
params[:nested_objects].each do |item|
#nest.nested.new(item.permit(:attr_one, :attr_two, :attr_three))
end
if #nest.save
....
else
....
end
end

why does klass.create(donor) always create a new donor while donors_controller#create does not

When I go to my donors_controller#create from a form in my view, it will update a record if it matches on ID or the collection of columns in my find_by_*
But if I call that create from a different controller, it always creates new records.
My donors_controller has a create method with:
def create
# need to find donor by id if given, else use find_or_create_by_blahblahblah
unless #donor = Donor.find_by_id(params[:donor][:id])
#donor = Donor.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(params[:donor])
end
if #donor.new_record?
...
else
...
end
end
My other controller has :
class_name = 'Donor'
klass = ActiveRecord.const_get(class_name)
... code to populate myarray with appropriate values
klass.create(myarray)
I am pretty sure myarray is populated with the necessary params since it creates valid records with everything in the right place. I can even run the same record through more than once and it creates duplicate (except for the Donor.id of course) records.
What am I doing wrong here?
I noticed I could do this in my other controller and it works, but why can't I call the create from the donors_controller and have it work without always creating a new record?
#klass.create(myarray)
#mydonor = klass.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(myarray)
#mydonor.save
your question is very unclear and hard to follow, so i'm not sure my answer will match your needs. It seems you're confusing a controller#create method with a model#create method.
On the model : it is a class method
create, as its name implies, instantiates a new object of the Donor class and calls save on it:
#donor = Donor.create
# is the same thing as
#donor = Donor.new
#donor.save
Active Record uses new_record? to determine if it should perform an insert or an update on the db when you call save on an instanciated record. So with create it will always be an insert since the record is inevitably new ; but if you call save on a record retrieved from the database, it will be updated.
On a controller : it is an instance method
... but does not directly manages persistence, that's the role of the model. It is an action for this controller ; it is called create for the sake of RESTful naming conventions. create is called on a controller instance when a POST request is routed to it ; its purpose is to manage that request, which often (but not always) means to create records using the appropriate model, using YourModelName.new or YourModelName.create.
so it is absolutely possible (but not advisable) to do:
class DonorsController < ApplicationController
def create
# only works well if params[:donor][:id] is nil,
# will raise an error if a record with the
# same id exists in the database
#donor = Donor.create(params[:donor])
end
end
as it is generally needed to perform several operations before saving your record, and to check if save is successful, the process is broken down :
class DonorsController < ApplicationController
def create
#donor = Donor.new(params[:donor])
# perform operations on #donor (instance of Donor class)
if #donor.save # save returns true if successfully performed, false either
# all is fine
else
# #donor could not be saved
end
end
end

Multi-page record creation

puts 'newbie question'
I have an account sign-up that spans multiple pages, but I'm not exactly wrapping my head around creating a new instance that is tied to a database but only adds to the database if all pages are completed.
I have three actions:
def index
#ticket = Ticket.new
end
def signup_a
end
def signup_b
end
The index page only collects a single text field, then passes that to populate the field in signup_a, then to b, which is where the record is finally added to the database. How do I go from passing the variable from index to A to B in a Ticket object without actually adding it to the DB?
Edit---
I think I got tripped up that the line
if #order.save
actually saves the object...I thought it just performed a check.
You can keep it in the session and save it in the database once all the steps are complete .

Resources