Issue with check times overlaps - ruby-on-rails

There are two models:
# Table name: activities_people
#
# activity_id :integer not null
# person_id :integer not null
# date :date not null
# id :integer not null, primary key
# == Schema Information
#
# Table name: activities
#
# id :integer not null, primary key
# name :string(20) not null
# description :text
# active :boolean not null
# day_of_week :string(20) not null
# start_on :time not null
# end_on :time not null
Relations:
activity.rb
has_many :activities_people
has_many :people, through: :activities_people
activity_people.rb
belongs_to :person
belongs_to :activity
I try to create validation that person can join to one activity taking place in specific date and time(start_on, end_on). If I will try sign up to another activity while before I joined to other exercises(same date, and times overlap) should throw error.
What I try:
def check_join_client
activities_date = person.activities_people.where('date = date', date: date)
if activities_date.exists && person.activities.where('id IN (?)', activities_date)
end
I don't know how to use create query(person.activities.where ...) to getting person activities related with activies_people. activities_date check if we joined to activities taking place in same date. Second I want get check start_on and end_on.
Thanks in advance.

If I'm understanding you correctly, you want to find the activites_people for a user that match a query by the date array and then raise an error unless an associated activity for those matched activities_people.
Your original code for check_join_client uses if incorrectly:
def check_join_client
activities_date = person.activities_people.where('date = date', date: date)
if activities_date.exists && person.activities.where('id IN (?)', activities_date)
end
To translate this to pseudocode, you're essentially saying:
result = query if result.condition_met?
However the if condition (the expression after the if) will be evaluated before you define results. It might be more clear if I show a correct approach:
result = query
return result if result.condition_met?
Now to go back to your question about loading associated records, try something like this:
activities_people_ids_matching_date = person.activities_people
.where(date: self.date)
.pluck(:id)
# use pluck to get an array of ids because that's all that's needed here
# not sure how you're getting the date variable, so I'm assuming self.date will work
# I can use `.any?` to see if the ids list is not empty.
condition_met = activities_people_ids_matching_date.any? &&\
person.activities
.where(activities_people_id: activities_people_ids_matching_date)
.any?
condition_met ? true : raise(StandardError, "my error")
There surely is a way to get this done with one query instead of two, but it seems like where you're at with Ruby concepts it's more important to focus on syntax and core functionality than SQL optimization.

The correct syntax (one of several options) is:
person.activities_people.where(date: date)

Related

Index jsonb column keys using GIN and pg_trgm, for ILIKE queries in Rails

I have a table "Leads" with the following structure :
# == Schema Information
#
# Table name: leads
#
# id :integer not null, primary key
# data :jsonb not null
# state :string
# priority :string
# lead_no :string
# user_id :integer
# location_place_id :string
# uuid :string
# agent_id :integer
# location_coordinates :geography point, 4326
# location :jsonb not null
# user_details :jsonb not null
# inventory_id :integer
# source_details :jsonb not null
# connect_details :jsonb not null
# referral_details :jsonb not null
# process_details :jsonb not null
# tags :jsonb not null
# created_at :datetime
# updated_at :datetime
# name :string
The user_details jsonb column stores data in the form- {name : "John Doe", country : "IN", phone_no : "123456789"}. I want to query my database columns using ILIKE for the name key as :
Lead.where("user_details->>name ILIKE ?","john%")
To achieve this, I created a migration as shown:
class AddIndexUserNameOnLeads < ActiveRecord::Migration[5.2]
def up
execute("CREATE INDEX leads_user_details_name_idx ON leads USING gin((user_details->>'name') gin_trgm_ops)")
end
def down
execute("DROP INDEX leads_user_details_name_idx")
end
end
This creates the necessary index. I have already enabled the pg_trgm extension in a previous migration. My structure.sql looks like :
Also, the corresponding schema.rb adds the following line for leads table -
t.index "((user_details ->> 'name'::text)) gin_trgm_ops", name: "leads_user_details_name_idx", using: :gin
However, when I try to query my database, it does a sequential scan.
On the other hand,if I create a gin index for the entire user_details column and then query using "#> {name: "john"}.to_json" it uses index for scan
My Rails version is 5.2.0 and PostgreSQL version is 12.5. How can I use ILIKE queries for this use case? Where am I going wrong? I'll be happy to provide more details if necessary.
An alternative is to tell your index to already sort the values using either upper or lower case, so that you can simply use LIKE in your queries.
CREATE INDEX leads_user_details_name_idx ON leads
USING gin(lower(user_details->>'name') gin_trgm_ops);
When querying this jsonb key you have to use the same function. Doing so the query planer will find your partial index:
SELECT * FROM leads
WHERE lower(user_details->>'name') ~~ '%doe%';
Demo: db<>fiddle
You table is probably just too small for an index scan to seem worthwhile. It looks like it only has 269 rows in it. You can set enable_seqscan=off to see if it uses the index then. Or you can just add a realistic number of rows to the table (and then VACUUM ANALYZE it)

get the higher value in ruby on rails

I am working with ruby on rails and i have a question.
I have a table who contains data like this:
{id: 471, from_city_id: '9', to_city_id: 14, product_id: 61, saving_in_from_currency: 262.0},
{id: 472, from_city_id: '9', to_city_id: 14, product_id: 61, saving_in_from_currency: 150.0}
Here the data model,
# == Schema Information
#
# Table name: pricings
#
# id :integer not null, primary key
# from_city_id :integer
# to_city_id :integer
# product_id :integer
# saving_in_from_currency :decimal(, )
# created_at :datetime
# updated_at :datetime
#
class Pricing < ActiveRecord::Base
belongs_to :from_city, :class_name => 'City'
belongs_to :to_city, :class_name => 'City'
belongs_to :product
end
I try to display a table in my view like this:
for example:
the user select the from_city_id, and i display for this id all the elements like this
to_city_id to_city_id to_city_id
product_id price price price
product_id price price price
product_id price price price
product_id price price price
For the moment i select the "saving_in_from_currency", by the from_city_id, the to_city_id and product_id.
But like you can see, in some case, only the id and the saving_in_from_currency change , and i want to display for each case like this the higher value, here "262.0".
Do you have any idea of how i could do this?(and sorry for my english)
Thank you.
It seems you'd likely benefit from using distinct in your query. I suspect something like this would accomplish what you desire. (Replace the model name of course, as it wasn't mentioned above).
Model.select(:from_city_id, :to_city_id, :product_id).distinct.order(saving_in_from_currency: :desc)
Edit:
Or, if you need access to attributes on the model other than from_city_id, to_city_id, product_id, this would also work.
# Replace with any additional constraints as needed
p = Pricing.all.order(saving_in_from_currency: :desc)
p.uniq! { |a| [a.from_city_id, a.to_city_id, a.product_id] }

Database design/Rails - design of balance between two users

I am looking for a way of designing a Balance model between two users in Ruby on Rails (but could be more general). Two users Alice and Bob would start with a balance of value zero with regards to the other one. Now, if Alice gets a +4, I want Bob to have -4. Here's what I came up with so far:
A balance with three fields : user_one, user_two, and one_to_two. That is if I want the balance for user_one to user_two I just take one_to_two, and if I want from two_to_one I take -one_to_two (just a method in the balance model). The problem is that I could not simply use 'has_many balances through' from the User model, since the balance would need to know if it's user_one or user_two who is calling (I would have to pass the User)
A balance with three fields again, but completely asymmetric: if I create a balance for Alice to Bob, I would have to create another instance of balance from Bob to Alice. This would double the storage needed for each 'balance' in the database, and would require updating the bob_to_alice everytime I update alice_to_bob.
Can anyone come up with something better, or explain the reason for choosing one of the above? Let me know if I can make anything clearer.
So, I think I found something pretty neat. It uses the fact that user alice is always the one with the smallest id (via :ensure_alice_before_bob). The rest should be clear.
The User model just has two extra "has_many :balances_as_alice, has_many :balances_as_bob". It could work with only one (for instance :balances_as_alice), but with those two present + dependent: :destroy you make sure to avoid orphan balances.
# == Schema Information
#
# Table name: balances
#
# id :integer not null, primary key
# alice_id :integer not null
# bob_id :integer not null
# alice_value :float default("0.0")
# created_at :datetime not null
# updated_at :datetime not null
#
class Balance < ActiveRecord::Base
belongs_to :alice, class_name: 'User'
belongs_to :bob, class_name: 'User'
before_validation :ensure_alice_before_bob
validates :alice_id, presence: true
validates :bob_id, presence: true, uniqueness: { scope: :alice_id}
validate :different_users
def value
#for_bob ? -alice_value : alice_value
end
def value=(new_val)
#for_bob? self.alice_value = -new_val : self.alice_value = new_val
end
def Balance.get_for alice, bob
user = 'alice'
alice, bob, user = bob, alice, 'bob' if alice.id > bob.id
balance= alice.balances_as_alice.find_by(bob: bob) ||
Balance.new(bob: bob, alice: alice, alice_value: 0.0)
balance.send("for_#{user}")
end
def for_bob
#for_bob = true; self
end
def for_alice
#for_bob = false; self
end
private
def ensure_alice_before_bob
return if self.alice_id.nil? || self.bob_id.nil?
unless self.alice_id < self.bob_id
self.bob_id, self.alice_id = self.alice_id, self.bob_id
end
end
def different_users
if self.alice_id == self.bob_id
errors.add(:bob_id, "Cannot be the same user")
end
end
end

Active Record specify a value select out from where() in update_all

I'd like to update multiple rows with update_all, but I need to use the existed value in one column selected from where(), how can I specify the value in update_all, it looks like this:
# == Schema Information
#
# Table name: records
#
# id :integer not null, primary key
# name :string
# game_id :integer
# score :integer
# date :date
Record.where(:name => "mike", :game_id => 1).update_all(score: "here I want to take out the score out and do some modifications and then store back")
Thanks a lot.
You must use SQL code to update in the manner which you want. Note that there is no way to use Ruby code to directly manipulate the score. You could create a database function if needed.
# Increment each score by a given amount
increment_amount = 10
Record.where(:name => "mike", :game_id => 1).update_all("score=score + #{increment_amount}")
That's not really what update_all is used for. update_all generally sets the value of every instance of a certain model to one value. You could not use update_all and instead do something like
Record.where(:name => 'mike', :game_id => 1).each do |record|
# record.score = record.score * 3 + 1
record.save!
end
Where the comment is just an example.

Rails filter on date returning no results

I'm trying to filter records that were assigned on particular day (today) using this query:
assignments = p.assignments.where("assigned_date = ?", Date.today)
Even though I know these records exist, I always get a blank result back.
I've also tried ...where(:assigned_date => Date.today) with no luck.
The database schema is:
# == Schema Information
#
# Table name: assignments
#
# id :integer not null, primary key
# name :string(255)
# rep :integer
# set :integer
# instructions :text
# created_at :datetime not null
# updated_at :datetime not null
# player_id :integer
# actual_percentage :float
# predicted_percentage :float
# assigned_date :date
#
And in the console, when I type
p.assignments.first.assigned_date == Date.today
it returns true.
Any idea what I'm doing wrong?
DateTime holds a date and a time, so you're looking for records that have a precise value, not just the same day.
assignments = p.assignments.where('assigned_date BETWEEN ? AND ?', DateTime.now.beginning_of_day, DateTime.now.end_of_day).all
should return what's expected
P.S.
all credits to Tadman

Resources