Rails .where for array isn't working as expected - ruby-on-rails

I'm looking to find records where #messages.visiblity (array) matches the current_user.id with this simple call:
#messages = Message.where(visibility: [current_user.id])
However it's not returning anything! So frustrating. Here's a sample of a message that should be in that #messages collection:
2.3.3 :007 > Message.last
Message Load (0.4ms) SELECT "messages".* FROM "messages" ORDER BY "messages"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Message id: 35, subject: "hello!", content: "can you see this", user_id: 40, created_at: "2020-08-06 03:22:59", updated_at: "2020-08-06 03:22:59", visibility: ["55", "49"], active: true>
Here the current user.id is 49.
It wont add that message to the #message instance until all user.ids are met, not just the one id that is 49. The record will be added to #messages if I do this:
#messages = Message.where(visibility: ["55", current_user.id]) #DON'T WANT THIS!
But that's ridiculous. Everywhere I'm looking says the first call should work, what am I doing wrong?
Here is my Message schema:
create_table "messages", force: :cascade do |t|
t.string "subject"
t.string "content"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "visibility", default: [], array: true
and here is the form that collects the data:
<%= form_with(model: #message) do |form| %>
<%= form.check_box(:visibility, {:multiple => true}, u.id, nil) %> - <%= u.email %>
<% end %>

this should work:
Message.where('visibility #> ARRAY[?]::string[]', ["49"])
or this:
Message.where("'49' = ANY(visibility)")

Following is the way how we can do it in databases irrespective of if database supports Array or not.
There is a pattern how serialized array is stored in database.
["55", "49"] will be stored as "55"\n "49"\n (or 55\n 49\n)
So you can search using LIKE operator
#messages = Message.where("visibility LIKE ?", "% #{current_user.id}\n%")
OR
#messages = Message.where("visibility LIKE ?", '% "#{current_user.id}"\n%')
Note: There is space between % and # in % #{current_user.id}

You can use this also
Message.where("visibility && array[?]",current_user.id.to_s)
I am casting current_user.id to STRING, because visibility is a STRING ARRAY and it cannot be compared to integer value of ID
You can also specify it as below:
Message.where("visibility && array[?]",[current_user.id.to_s])
Or with Multiple value array as below:
Message.where("visibility && array[?]",['55','49'])
Multiple values are compared as AND operation.

Related

Ruby on Rails performance of search engine with indexed column

Hi i'm try to check search engine's performance of my ROR application.
I have 4 search input forms : title, content, created_on (date) and updated_on (date)
I want to check performace of search depending on the presence or absence of an index. (in my case, index presence on created_on and absence on updated_on)
My controller of Post
def index
search_start_time = Time.now
#posts = Post.search(params[:title], params[:content], params[:created_on], params[:updated_on])
# this line for check performance of search
puts Time.now - search_start_time
end
My schema
create_table 'posts', force: :cascade do |t|
t.string 'title', null: false
t.string 'content', null: false
t.date 'created_on', null: false, index: true
t.date 'updated_on', null: false
end
In my post.rb, i maked search method like this
def self.search(title, content, started_on, finished_on)
where([
"title LIKE ? AND content LIKE ? AND CAST(started_on AS text) LIKE ? AND CAST(finished_on AS text) LIKE ?",
"%#{title}%", "%#{content}%", "%#{started_on}%", "%#{finished_on}%"
])
end
With my code, i performance but there were not big difference with search performance of "indexed" and "not indexed" columns.
Is there a problem with my code? Or does the index not affect the search results?
The number of records is 10 million, and an indexed column always comes out similar to the speed of an unindexed column.
I tried to change my search method like this ->
def self.search(title = '', content = '', started_on = '', finished_on = '')
But there was not difference.

Select query while grouping with ActiveRecord does not work

I have following query in my backend:
#books = Book.select("city, sum(rating)").group("city")
The table in the database is as follows:
create_table "books", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "author_id"
t.integer "rating"
t.string "city"
t.index ["author_id"], name: "index_books_on_author_id"
end
The result that I get is as follows:
#<ActiveRecord::Relation [#<Book id: nil, city: "barcelona">, #<Book id: nil, city: "madrid">]>
I am using sqlite, just to practice AR.
In sqlite, the command that I would use shows the results that I expect:
sqlite> SELECT city, sum(rating) FROM books GROUP BY city;
city sum(rating)
---------- -----------
barcelona 11
madrid 10
Why am I not seeing the sum of the ratings with ActiveRecord?
Why am I seeing the ids, if I didn't select them?
Thanks a lot
You're seeing the ids because inspect is not perfect in ActiveRecord.
You're not seeing the sum of the ratings because inspect is not perfect in ActiveRecord.
If you add an AS to your select:
#books = Book.select("city, sum(rating) AS rating_sum").group("city")
# => #<ActiveRecord::Relation [#<Book id: nil, city: "barcelona">, #<Book id: nil, city: "madrid">]>
#books.first.rating_sum
# => 11
you will be able to call .rating_sum on each of the returned elements, but you still won't see the sum in your console when inspecting an element.
In this case, you may also access the sum without using AS by calling .sum on your results,
#books.first.sum
but once your query gets more complex, you will need to specify the attribute names explicitly by using AS in your query.
As you've probably noticed, the ids you are seeing are null, it's because inspect for ActiveRecord always displays id of a model and since your query hasn't returned it, nothing is assigned to it.

sum hash values inside db column and place result in another

I have a table called google_records in which every row is a snapshot of a google adwords account for that day.
Here is the schema -
create_table "google_records", :force => true do |t|
t.string "user_id"
t.string "date"
t.text "stats"
t.text "account_name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.decimal "total_cost", :precision => 12, :scale => 2, :default => 0.0, :null => false
t.integer "total_conversions", :default => 0, :null => false
end
add_index "google_records", ["date"], :name => "index_google_records_on_date"
stats contains a hash of the day's stats by campaign and an entry would look something like this:
:Carpet Cleaning:
:conversions: 3
:cost: 391.47
:Upholstery Cleaning:
:conversions: 0
:cost: 69.96
:Air Duct Cleaning:
:conversions: 0
:cost: 8.68
I just added those total_cost and total_conversion columns and I'd like to update those with the totals for each respective value each day.
I can get the total I want to work in my console like so(the cost total doesn't match up with the sample I gave but its only because I shortened the sample to fit better - the total is correct) -
user = User.find(6)
GoogleRecord.where(user_id: user).where(date: "20140328").map {|m| m.stats}.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
=> 660.26
I'd like to update all records in the table this way for conversions and cost and save them but when I try something like:
GoogleRecord.all.each do |g|
g.map {|m| m.stats}.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
end
I keep getting NoMethodError: undefined method map for GoogleRecord:0x5f09868
It seems like since I'm just grabbing one record in the example that is working, I should be able to apply code to each record.
Inside your example code, what the where method return is something like array, so map can work. But inside the GoogleRecord.all.each block, the g is reference to the db record. Remove the first map call, and see whether it will work.
GoogleRecord.all.each do |g|
g.stats.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
end

Select attribute which is not current_user.id

I'm creating a messaging system in Rails, and in order to display conversation partners, I get their ID from the messages that current_user is included in:
Message.where("to_id = ? or user_id = ?", current_user.id, current_user.id)
However, as I need only the other user's id from that query, I need to select it, and I would like to choose either :to_id or :user_id whichever is not equal to current_user.id
Something like:
Message.where("to_id = ? or user_id = ?", current_user.id, current_user.id).select(:to_id, :user_id unless current_user.id)
Thank you in advance!
Edit: Here is the Messages table simplified:
create_table "messages", force: true do |t|
t.integer "to_id" -> User to whom the message was forwarded to
t.integer "user_id" -> User who wrote the message
end
if your just trying to get the IDs:
Message.where("to_id = ? or user_id = ?", current_user.id, current_user.id).
map { |m|
m.user_id == current_user.id ? m.to_id : m.user_id
}
this will give you and Array like
# => [3771, 3611, 3749, 3638, 3697, 3786]

Column does not exist error in PostgreSQL database and Rails 3 app

I am getting the error below when trying to query my PostgreSQL database. I can view the table and all columns in pgAdmin and even perform a select *, so I know the table and column exists. Any help with this will be greatly appreciated.
Here is the error I am getting:
PG::Error: ERROR: column "fi_ase" does not exist
Here is the schema for the table in question. It was generated with a migration as part of a Rails 3.2 app.
create_table "certificates", :force => true do |t|
t.integer "profile_id"
t.boolean "FI_ASE"
t.boolean "FI_AME"
t.boolean "FI_INSTA"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "C_ASEL"
t.boolean "C_AMEL"
t.boolean "C_ASES"
t.boolean "C_AMES"
t.boolean "ATP_ASEL"
t.boolean "ATP_AMEL"
t.boolean "ATP_ASES"
t.boolean "ATP_AMES"
t.boolean "GI_Basic"
t.boolean "GI_Advanced"
t.boolean "GI_Instrument"
end
Here is my query/method in Rails:
def self.search(city, state, zip, *certs)
query_obj = joins(:profile => [:addresses, :certificate])
query_obj = query_obj.where("city like ?", "%#{city}%") unless city.blank?
query_obj = query_obj.where("state = ?", state) unless state.blank?
query_obj = query_obj.where("zip like ?", "%#{zip}%") unless zip.blank?
query_obj = query_obj.where("FI_ASE = ?", true) unless certs[0].blank?
query_obj
end
I get the same error when running the following SQL statement directly in my pgAmin SQL Editor:
select *
from contacts c
inner join profiles p on c.id = p.contact_id
inner join addresses a on p.id = a.profile_id
inner join certificates ct on p.id = ct.profile_id
where ct.FI_ASE = true
Rails will double quote the column names when it generates them. For example, the CREATE TABLE for your table will look like this when PostgreSQL sees it:
create table "certificates" (
-- ...
"FI_ASE" boolean,
When an identifier is double quoted, it is case sensitive. But, PostgreSQL will normalize unquoted identifiers to lower case so when you say this:
query_obj.where("FI_ASE = ?", true)
the SQL will come out as:
where FI_ASE = 't'
but, since your FI_ASE is not quoted, PostgreSQL will see that as:
where fi_ase = 't'
However, your table doesn't have an fi_ase column, it has an FI_ASE column.
Now that we know what's wrong, how do we fix it? You can manually quote the column name all the time:
where('"FI_ASE" = ?', true)
or you could let ActiveRecord do it (but make sure you use the right case):
where(:FI_ASE => true)
or best of all, recreate the table using lower case column names so that you don't have to quote things.

Resources