Searching multiple tables in Postgres and Rails - ruby-on-rails-3

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

Related

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

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"

What is the most efficient way to determine if a record with a given property already exists in a Rails database?

I am trying to avoid excess DB hits/queries in my rails site. Given an array of URLs, i need to know if a corresponding Website has already been created in my database or not, so that I can create it if needed.
1) The first method selects all the urls from the array and then queries this smaller set to determine if a given URL has been created
urls = ["https://www.google.com/", ... "https://stackoverflow.com"]
my_sites = Website.where url: urls
urls.each_with_index do |url, i|
this_site = my_sites.find_by url: url
if this_site == nil
#do stuff
end
end
2) The second method selects each site from the record individually
urls = ["https://www.google.com/", ... "https://stackoverflow.com"]
urls.each_with_index do |url, i|
this_site = Website.find_by url: url
if this_site == nil
#do stuff
end
end
3) Another way? Neither of these seem too efficient, they are both opening a lot of DB connections I believe.
existing_urls = Website.where(url: urls).pluck(:url)
urls_to_create = urls - existing_urls
urls_to_create.each do |url|
# create the website, etc
end
This will perform a single SQL query to fetch all existing urls, using pluck to avoid instantiating ActiveRecord objects. It then removes that list from the supplied list and then sets up the remaining urls.
P.S. Neither if your options will "open a lot of DB connections." They will however generate a lot of SQL queries. Which is probably what you meant, but wanted to clarify.
urls.each do |url|
check(url)? false : create_db
end
def create_db
db create
end
def check ur
if Website.find_by_url(ur)
return true
else
return false
end

How do I paginate a pre-sorted record collection using will_paginate?

How do I paginate a pre-sorted record collection using will_paginate?
Environment: Ruby 2.0.0, Rails 4.0.3, Windows 8.1, PostreSQL, Datatable 1.12.2, Will_Paginate 3.0.5
Using Railscast 340 code, I am trying to finalize my datatable implementation. I have several columns that I need to sort independently because they exist in associations and cannot be accessed directly. I've only shown one column in one direction for simplicity.
The sort is working in both the standard case and in the "product_location" case. The correct records are available in the record collection and they are in the correct order in both cases.
In the "product_location" case, when I try to paginate the result, will_paginate seems to retrieve records from the table instead of using my record collection. I end up with the wrong records in the wrong order. This action doesn't make sense to me, so I must be missing something.
The standard sort is working in both directions on all columns.
My code is as follows, and after that is the original code from Railscast 340:
def products
#products ||= fetch_products
end
def fetch_products
case sort_column
when "product_location"
products = Product.all
products.sort_by!{|product| product.readable_loc} # product_location sort
else
products = Product.order("#{sort_column} #{sort_direction}") # standard sort
end
# products is correctly sorted in both cases at this point
products = products.page(page).per_page(per_page)
# At this point, products is correctly sorted in the standard case but not in the "product_location" case
# In "product_location" case, pagination seems to be accessing the table directly again, not using the record collection?
if params[:sSearch].present?
products = products.where("stock_number like :search", search: "%#{params[:sSearch]}%")
end
products
end
The original code is:
def products
#products ||= fetch_products
end
def fetch_products
products = Product.order("#{sort_column} #{sort_direction}")
products = products.page(page).per_page(per_page)
if params[:sSearch].present?
products = products.where("name like :search or category like :search", search: "%#{params[:sSearch]}%")
end
products
end
It is because Product.all returns instance of ActiveRecord::Relation and products.sort_by! fetches all records from database a sort them with ruby. Than if you call products = products.page(page).per_page(per_page) it creates new sql query with new limit and offset to fetch records on given page.
So if you want to use will_paginate for pagination, you cannot use .sort_by! method for data sorting, but you have to order records with Products.order which will sort records via SQL in the database.

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

Rails 3 Applying limit and offset to subquery

I have a query that goes something like this (in song.rb):
def self.new_songs
Song.where(id: Song.grouped_order_published).select_important_stuff
end
Later on in my app, it is then passed the limit and offset, lets say in the controller:
#songs = Song.new_songs.limit(10).offset(10)
The way my app is structured, I'd like to keep this method of setting things, but unfortunately it is really slow as it is limiting the outer query rather than the subquery.
Is there a way I can expose the subquery such that it receives the limit and offset rather than the outer query?
Edit: I should add I am using postgres 9.2.
Edit 2: The reason why I want to do it in this fashion is I am doing pagination and I need to get the "count" of the total number of rows. So I do something like this:
#songs = Song.new_songs
...
#pages = #songs.count / 10
...
render #songs.limit(params[:page]).offset(0)
If I were to change it somehow, I'd have to redo this entirely (which is in a ton of places). By not limiting it until it's actually called, I can do the count in between and then get just the page at the end. I guess I'm looking more for advice on how this can be done with the inner query, without becoming horribly slow as the database grows.
I could not try the solution and I am not a ruby expert either, but as far as I understand the problem you would need an object that passes all method-calls but limit and offset onto the full query and store the limited sub_query in the meantime.
It could probably look like this:
class LimitedSubquery < Object
# sub_query has to be stored so we can limit/offset it
def initialize(sub_query)
#sub_query = sub_query
end
# Make sure everybody knows we can be used like a query
def self.respond_to?(symbol, include_private=false)
super || full_query.respond_to?(symbol, include_private)
end
# Missing methods are probably meant to be called on the whole query
def self.method_missing(method_sym, *arguments, &block)
if full_query.respond_to?(method_sym)
full_query.send(method_sym, *arguments, &block)
else
super
end
end
# Generate the query for execution
def self.full_query
Song.where(id: #sub_query).select_important_stuff
end
# Apply limit to sub_query
def self.limit(*number)
LimitedSubquery.new(#sub_query.limit(*number))
end
# Apply offset to sub_query
def self.offset(*number)
LimitedSubquery.new(#sub_query.offset(*number))
end
end
And than call it like
def new_songs
LimitedSubquery.new(Song.grouped_order_published)
end
Please edit me if I got something wrong!
Regards
TC
You should consider using the will_paginate gem. This keeps you away form the hazzle to calculate all this by hand ;-)