How to toggle a boolean field for multiple records generating just one SQL query - sql

I'm trying to write a migration to update a boolean field where the field will mean the exact opposite of what it means currently. Therefore I need to toggle every record to update the records where this field is true to false and vice-versa.
Example:
class ChangePostsVisibleToArchived < ActiveRecord::Migration[6.1]
def change
rename_column :posts, :visible, :archived
# Toggle all tracked topics
# What I DON'T want to do:
Post.all.each { |post| post.toggle! :archived }
end
end
The way I described above will generate one SQL command per Post record.
Is there a way I can toggle all records within a single SQL command using rails ActiveRecord syntax?

Post.update_all "archived = NOT archived"

Related

Rails 5 - using synthetic attributes in queries?

I'm not sure if synthetic attributes are capable of being used this way, but I have an account model with two boolean fields, :is_class_accout and :is_expense_account.
I created a an attribute which is not persisted in teh db called :is_synthetic which return true if either :is_class_account is true or :is_expense_account is true.
What I want to do is write a query like:
Account.find_by_project_id(project_id).where(is_synthetic: true)
This returns a PG error because the resulting query is looking for the is_synthetic field in the db, and of course it's not there.
Am I doing something wrong, or is this expected behavior?
The code I am using is:
class Account < ApplicationRecord
attribute :is_synthetic, :boolean
def is_synthetic
self.is_class_account || self.is_expense_account
end
The behaviour is expected. Two things here:
The example query is wrong since find_by_project_id would return the first matching Account record and not a collection to call where on.
In your case all you have to do is retrieve the Account records and then filter them:
class Account < ApplicationRecord
def is_synthetic
self.is_class_account || self.is_expense_account
end
end
# Returns an Array of accounts
Account.where(project_id: <queried_project_id>).select(&:is_synthetic)

Update more record in one query with Active Record in Rails

Is there a better way to update more record in one query with different values in Ruby on Rails? I solved using CASE in SQL, but is there any Active Record solution for that?
Basically I save a new sort order when a new list arrive back from a jquery ajax post.
#List of product ids in sorted order. Get from jqueryui sortable plugin.
#product_ids = [3,1,2,4,7,6,5]
# Simple solution which generate a loads of queries. Working but slow.
#product_ids.each_with_index do |id, index|
# Product.where(id: id).update_all(sort_order: index+1)
#end
##CASE syntax example:
##Product.where(id: product_ids).update_all("sort_order = CASE id WHEN 539 THEN 1 WHEN 540 THEN 2 WHEN 542 THEN 3 END")
case_string = "sort_order = CASE id "
product_ids.each_with_index do |id, index|
case_string += "WHEN #{id} THEN #{index+1} "
end
case_string += "END"
Product.where(id: product_ids).update_all(case_string)
This solution works fast and only one query, but I create a query string like in php. :) What would be your suggestion?
You should check out the acts_as_list gem. It does everything you need and it uses 1-3 queries behind the scenes. Its a perfect match to use with jquery sortable plugin. It relies on incrementing/decrementing the position (sort_order) field directly in SQL.
This won't be a good solution for you, if your UI/UX relies on saving the order manually by the user (user sorts out the things and then clicks update/save). However I strongly discourage this kind of interface, unless there is a specific reason (for example you cannot have intermediate state in database between old and new order, because something else depends on that order).
If thats not the case, then by all means just do an asynchronous update after user moves one element (and acts_as_list will be great to help you accomplish that).
Check out:
https://github.com/swanandp/acts_as_list/blob/master/lib/acts_as_list/active_record/acts/list.rb#L324
# This has the effect of moving all the higher items down one.
def increment_positions_on_higher_items
return unless in_list?
acts_as_list_class.unscoped.where(
"#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
).update_all(
"#{position_column} = (#{position_column} + 1)"
)
end

Searching multiple tables in Postgres and Rails

I use PostgreSQL's full text search capabilities, which works fine. All the relevant columns are indexed so it's nice and speedy:
def self.text_search(query)
if (query.present?)
# search(query)
where(
"to_tsvector('english', title) ## plainto_tsquery(:q)",
q: query
)
else
scoped
end
end
But now I also want to search through related abbreviations:
def self.text_search(query)
if (query.present?)
# search(query)
includes(:abbreviations).where(
"to_tsvector('english', articles.title) ## plainto_tsquery(:q)"+
" or to_tsvector('english', abbreviations.abbreviation) ## plainto_tsquery(:q)",
q: query
)
else
scoped
end
end
This works, but now my queries take 2.5+ seconds! How do I remedy this? I was thinking that maybe this is a Rails inefficiency, so I could best perform raw SQL. But how do I do that and still get back a ActiveRecord relation?
What I did as a workaround, added a str_* column to my main table, and update this column when an element is saved, and then search over that column:
before_validation(on: :create) do
self.str_abbreviations = join_abbreviations()
... etc ...
true
end

Truncate table on migration down method of ActiveRecord Rails 3.1

I have the following defined on my up method on my migration to set initial data:
def up
Color.create!({:id=>1,:name=>"",:color=>"FF6633"})
Color.create!({:id=>2,:name=>"",:color=>"93B233"})
Color.create!({:id=>3,:name=>"",:color=>"4D90D9"})
Color.create!({:id=>4,:name=>"",:color=>"C43092"})
end
Is there any truncate directive I can put on the down method like:
def down
Color.truncate
end
Or since I'm setting the IDs on the create should I use only the destroy_all method of the model Color ?
You can simple use this in your up method, this will solve your both truncate and id resetting problem also.
def up
ActiveRecord::Base.connection.execute("TRUNCATE table_name")
Color.create!({:id=>1,:name=>"",:color=>"FF6633"})
Color.create!({:id=>2,:name=>"",:color=>"93B233"})
Color.create!({:id=>3,:name=>"",:color=>"4D90D9"})
Color.create!({:id=>4,:name=>"",:color=>"C43092"})
end
Cheers!
Firstly, you don't have to pass :id into create! because ActiveRecord will automatically handle that, thus :id likely to get ignored (standard case assumed).
Secondly, it is not a good practice to use ActiveRecord query builder in migration because should the model Color name be changed, you are to have a broken migration. I highly recommend you to use pure SQL and execute that query with execute().
Thirdly, for the #down method, you shouldn't truncate the table. You should destroy those 4 colors that you created in #up.
Here's how I would write it:
def down
colors = ["FF6633", "93B233", "4D90D9", "C43092"]
Color.where(:color => colors).destroy_all
end

Ruby On Rails: How to run safe updates

I'm using RoR and I want to do concurrency safe update queries. For example when I have
var user = User.find(user_id)
user.visits += 1
I get the following SQL code:
SELECT * FROM Users WHERE ID=1 -- find user's visits (1)
UPDATE Users SET Visits=2 WHERE ID=1 -- 1+1=2
But if there are several queries taking place at the same time, there will be locking problems.
According to RoR API I can use :lock => true attribute, but that's not what I want.
I found an awesome function update_counters:
User.update_counters(my_user.id, :visits => 1)
This gives the following SQL code
UPDATE Users SET Visits=Visits+1 WHERE ID=#something
Works great!
Now my question is how can I override the += function to do the update_counters thing instead?
Because
user.visits += 1 # better
User.update_counters(my_user.id, :visits => 1) # than this
UPDATE
I just created the following function
class ActiveRecord::Base
def inc(column, value)
User.update_counters(self.id, column => value)
end
end
Are there any other better ideas?
Don't know about better ideas, but I would define that method to the User class, not the ActiveRecord. And maybe increment_counter (that uses update_counters) would make it a bit more readable?
def inc(column, value)
self.increment_counter(column, value)
end
Haven't tested that and not saying this is definitely better idea, but that's probably how I'd do it.
Update:
And AFAIK you can't override the "+=", because "a += b" just a shortcut for "a = a + b" and you probably don't want to override "=" to use the update_counters :)