Mass/bulk update in rails without using update_all with a single query? - sql

I want to update multiple rows using single query of active record. I don't have to use update_all because it skips validation. Is there any way to do this in rails active record.?

Mass update without using update_all can be achievable using activerecord-import gem.
Please refer to this gem for more information.
Methods with detail.
Example:
Lets say there is a table named "Services" having a "booked" column. We want to update its value using the gem outside the loop.
services.each do |service|
service.booked = false
service.updated_at = DateTime.current if service.changed?
end
ProvidedService.import services.to_ary, on_duplicate_key_update: { columns: %i[booked updated_at] }
active-record import by default does not update the "updated_at" column. So we've to explicitly update it.

If you want to update multiple records without instantiating the models, update_all method should do the trick:
Updates all records in the current relation with details given. This method constructs a single SQL UPDATE statement and sends it straight to the database. It does not instantiate the involved models and it does not trigger Active Record callbacks or validations. However, values passed to #update_all will still go through Active Record’s normal type casting and serialization.
E.g:
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
As I understood, it even accepts an array as a parameter, allowing us to provide different hashes to update different records in their corresponding order, like in SQL UPDATE statement. Correct me, somebody, if I'm wrong.

It sounds like you are looking for Update records - from the documentation Updating multiple records; different col with different data
You can do like this Example:
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)

Related

Querying ActiveRecord Syntax

I'm working on a task where I have to define a function that attaches something to a column in my DB, however I'm quite new and despite looking at the ActiveRecord documentation I don't appear to be able to grab the column I'm looking for.
For example, I have a table with many columns including 'State' and 'Phase', I was able to grab state with the following code:
CaseFileStatus.where(state: case_file.state).first
However I can't somehow manage to grab the 'Phase' column now, as shown below.
CaseFileStatus.where(state: "case_file.phase")
CaseFileStatus Load (2.5ms) SELECT "case_file_statuses".* FROM "case_file_statuses" WHERE "case_file_statuses"."state" = $1 [["state", "case_file.phase"]]
=> []
I'm sure it's a super basic error, but how should I be structuring this query?
If you need to query records based on the phase column, you should do something like this:
CaseFileStatus.where(phase: case_file.phase)

Rails, check database if uploading data in CSV already exists

I'm new to Rails and I would like to know if I can check if data already exists in my database while uploading new data from CSV. So far I haven't found this on the net.
I use a Postgres database. I don't know if I have to check it in Rails or in Postgres. In my database there are some columns like id, personal_id, cost_center. So I will check if one (or more) of my new data has the same personal_id with the the same cost_center while uploading.
How can I do this?
UPDATE
I've tried the solution of #huan son, it works but not the way I need it. So I tried different things and I think a SQL query is in my case the best choice.
DELETE FROM bookings WHERE id NOT IN (SELECT MIN(id) FROM bookings GROUP BY personal_id, wbs, date, hours, cost_center)
Booking.delete_all.where('id NOT IN (?)', Booking.select('MIN(id)').group(:personal_id, :wbs, :date, :hours, :cost_center).map(&:id))
My SQL query works like the way I want it but I don't know the right "translation" into rails because with the second code above my whole bookings table gets deleted
Solution
The solution for my problem is:
Booking.delete_all(['id NOT IN (?)', Booking.group(:personal_id, :wbs, :date, :hours, :cost_center).pluck('MIN(id)')])
Those are unique scopes.
You need to define those first in your database migration so postgres is making sure there is no double-value wie [key, key1, key2...]
add_index :table, [:personal_id, :cost_center_id], :unique => true
then you need to go into your rails-model and catch that uniqueness at validations.
validates_uniqueness_of :personal_id, :scope => :cost_center_id
with that, rails is querying every time before creating a object, the database and check if something with the unique-pair-values already exists. if so, its adding it to the #errors of the model, so the model can't be saved
You can use uniqueness validation, but in my experience when importing data from CSV, the problem is that if the item already exists, all the validation does is stop the record being saved. In most examples, you usually want to do something with the matched record. Therefore, I'd suggest you also look at find_or_initialize_by. This allows you to also update existing records from imported data.
So if you have Thing model with a name and cost for example, you may want to identify existing things by name, and update their costs. And create new things where no matching name exists. The following code would do that:
name, price = some_method_that_gets_name_and_price_from_csv
thing = Thing.find_or_initialize_by name: name
thing.price = price
thing.save
Also have a look at find_or_create_by which can be more suitable in some situations. I'd also still keep the validation of uniqueness in the model. I just wouldn't use validation to handle how the data was imported.

Use Rails Model method in Active Query

I have an orde field on one of my models, that model also has a is_milestone? method which returns true or false depending on whether the order is either a 50th or 100th milestone.
I wish to select all the items that are milestones but can this be done by using the is_milestone? method?
I currently have:
#milestones = Model.where(:is_milestone?).order('order ASC')
But it doesn't work and I think I have the syntax wrong but unsure how it should be written.
Any thoughts?
No - You cannot ask the database to do a query which requires interaction with your Ruby code.
The only way you'd be able to get this to work is to create an is_milestone attribute on your model and store the boolean value within the database. You could perhaps populate this value with an after_save callback which counted the milestones and set the value to true if it was either the 50th or 100th one.

Rails 3 Searching Multiple Models by created_at using sunspot

I'm trying to get a "What's new" section working in my Rails app that takes into account new records created for various tables that don't share any relationships. The one thing they do have in common is that they all have a created_at field, which I'm going to use to determine if they're indeed "new" and then I'm wanting to sort the results by that common field. I tried doing this with Sunspot, but I couldn't figure out how to make use of the the result set returned from the Sunspot search...
For instance in my Uploads and Article models I have:
searchable do
time :created_at
end
and in my search action I'll do this:
#updates = Sunspot.search(Upload,Article) do
with(:created_at).greater_than(1.hour.ago)
end
Which does seem to return something, if I do an #updates.total it returns the number of records I was expecting to find. Beyond this I'm not sure how to actually make use of the records. What I'd like to do is send #updates to a view and determine the model type of each record and then proceed to print out the relevant information, i.e names, descriptions, parent/child record information (for instance upload.user.username).
I might be going at this all wrong, perhaps there's a better option than sunspot for the simple search I'm attempting to perform?
Refer readme for details of how to use the search results. The method you are looking for is "results", which will give you first 30 results, by default:
#updates.results # array of first 30 results

Rails 3 - defining :order using a combination of attributes, while treating the *latest* record differently

My Application has the following :order requirements:
Order by the importance (measured by say number of likes) AND
Order by created_at DESC
So I am currently using the following to define the :order
:order => 'model.likes_count DESC, created_at DESC'
However, in my views, I create new model entries using AJAX and therefore, I reload the partials where I display the database entries for this particular model and would like to use jQuery effects to (say) highlight the record that was just created.
Now due to the above :order definition, the record just created would not show up as the first one if an older record has greater number of 'likes'.
Is there a way to define an order which takes into account the "latest" record differently and then order the rest of the records as per the defined order? If possible what would be the clean way to achieve this.
Thanks!
The Rails API does not have any functionality to treat a newly created record differently.
Your options are:
Do not select the new record in the query, eg. with .where("id <> ?",idofnewrecord)
You can select the new record in a separate query if required
Or, select the records as normal, then find the new record in ruby, eg:
#newrecord = #records.find{|r|r.id==idofnewrecord}
or to delete it from the array of records:
#newrecord = #records.delete_if{|r|r.id==idofnewrecord}
If you don't know the id, you may be able to filter it out with some other attribute as well, eg. created_at.