I am trying to compare hashes, they reference a complex set of associations. Payments are managed by an engine within the app so there is no belongs_to: / has_many: relationships between it and users(which is on the main application). However i can pull user from the main app using a service.
I have a Payments table - in engine
# Table name: payments
#
# id :integer not null, primary key
# account_id :integer
# currency :string(255)
....
The Account table just maps users to accounts - in engine
# Table name: account
#
# id :integer not null, primary key
# user_id :integer
....
The User table also has a column for currency, this is to mark what currency he opted to pay in during the sign-up phase.
# Table name: user
#
# id :integer not null, primary key
# currency :string(255)
....
We have an issue where sometimes users pay in different currencies, I would like to build a hash where :payment_id => true/false(where this would be user.currency == payment.currency), something where i can count the offences I was thinking of saving the payment_id just incase they wanted detailed error data.
What I ended up doing was.
Building a payments Hash with account_id: currency
Building a accounts Hash using only account_ids found in step 1 id: user_id
Rebuilding the payments hash by replacing account_id: with user_id:
Building a users Hash with users_id: currency
Iterating through each user_id and ticking a counter when payment_hash[user_id] != user_hash[user_id]
looks something like this
def inconsistancy_count(payments)
#list of account ids involved with the payments made
matching_account_ids = Recon::Account.where(account_id: payments).map(&:id)
# list of users that are attached to those accounts above
user_ids = Recon:Account.where(account_id: matching_account_ids).map(&:user_id)
# Payment hash mapping account_id and currency
payment_currency_hash = Recon::Payment.where(account_id: matching_account_ids).reduce({}) {|hash, payment| hash.merge(payment.account_id => payment.currency)}
#build a hash using the user service.
main_app_user_currency_hash = Recon.user_service.get_user_info_from_ids(user_ids)
inconsistancy_count = 0
matching_user_ids.each do |user|
inconsistancy_count += 1 if main_app_user_currency_hash[user] != payment_currency_hash[user]
end
inconsistancy_count
end
The above won't work because the payments hash is still based off account_id and not user_id.
I feel my route is very round about it seems as if this method has a ton of things it should do. Is there a better way of accomplishing this
Related
Context
Imagine the following: I have a model Sector that contains an ID as primary key (which I want to keep) and that model has two attributes that are unique together and with an index about their location (ie. Sectors cannot be in the same position).
# Table name: sector
#
# id :bigint not null, primary key
# x :bigint not null
# y :bigint not null
# attrA :datetime
# attrB :boolean
# attrC :string
# (etc)
#
# Indexes
#
# sector_index (x,y) UNIQUE
Now, let's imagine that we have objects that are at a specific location, and that location may change (objects move around).
# Table name: object
#
# id :bigint not null, primary key
# x :bigint not null
# y :bigint not null
Problem
How to join these tables efficiently??
Option 1 - ID as foreign key
The obvious off the shelf solution to join these two tables is to create a belongs_to :sector, which would need a sector_id foreign key. However, every time we need to update a large number of objects' positions, we would need to query the sector table to get the ID of the sector containing X and Y. (eg, let's say we need to move all objects 3 sections in the Y direction)
Option 2: Ditching the belongs_to and use a model attribute
The other choice is to have
class Object < ApplicationRecord
def sector
Sector.where(x: x, y:y)
end
end
The obvious disadvantage is that this does not allow any preloading or table joins, and will result in n+1
Option 3 - using Gem composite primary key
The gem Composite primary keys - https://github.com/composite-primary-keys/composite_primary_keys - allows the use composite primary keys, but I would like to keep the ID as a primary key.
Question
Can we somehow use the unique index to create a belongs_to association that can take advantage of the Rails ActiveRecord helpers and methods such as includes and preloads and joins? Maybe create additional (non primary) composite key in the Sector?
Other references I found:
Shopify composite primary keys - https://shopify.engineering/how-to-introduce-composite-primary-keys-in-rails
I am attempting to access the results of a join where columns of both tables are specified as part of a projection.
I have 2 models (Rails 4.2.11; Arel 6.0.4; Ruby 2.5.3)
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
class User < ActiveRecord::Base
has_many :photos
end
# Table name: photos
#
# id :integer not null, primary key
# name :string(255)
# created_by_id :integer
# assigned_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
class Photo < ActiveRecord::Base
belongs_to :user
end
creators = User.arel_table.alias('creators')
updaters = User.arel_table.alias('updaters')
photos = Photo.arel_table
photos_with_credits = photos.
join(photos.join(creators, Arel::Nodes::OuterJoin).on(photos[:created_by_id].eq(creators[:id]))).
join(photos.join(updaters, Arel::Nodes::OuterJoin).on(photos[:assigned_id].eq(updaters[:id]))).
project(photos[:name], photos[:created_at], creators[:name].as('creator'), updaters[:name].as('editor'))
# generated SQL
SELECT photos.name, photos.created_at, creators.name AS creator, updaters.name AS editor
FROM photos
INNER JOIN (SELECT FROM photos LEFT OUTER JOIN users creators ON photos.created_by_id = creators.id)
INNER JOIN (SELECT FROM photos LEFT OUTER JOIN users updaters ON photos.updated_by_id = updaters.id)
How I'd like to process the result
photos_with_credits.map{|x| "#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}"}.join('; ')
This may be a very dumb question, but ...
I have not been able to find a way to use the SelectManager result to produce a meaningful output since map was deprecated (& removed) from the SelectManager class.
I would appreciate your help.
Simplified the activerecord / arel query builder
amended code
photos_with_credits = Photo.select([photos[:name], photos[:created_at], creators[:name].as('creator'), updaters[:name].as('editor')]).
joins(photos.outer_join(creators).on(photos[:created_by_id].eq(creators[:id])).join_sources).
joins(photos.outer_join(updaters).on(photos[:assigned_id].eq(updaters[:id])).join_sources)
photos_with_credits.map do |photo|
puts "#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}".light_blue
end
amended SQL (simpler)
SELECT photos.name, photos.created_at, creators.name AS creator, updaters.name AS editor
FROM photos
LEFT OUTER JOIN users creators ON photos.created_by_id = creators.id
LEFT OUTER JOIN users updaters ON photos.assigned_id = updaters.id
We should figure out a way to trigger SelectManage and get ActiveRelation result, then we can iterate it by using map, etc.
So I would like to suggest:
Photo.join(photos_with_credits.join_sources).map do |photo|
"#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}"}.join('; ')
end
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)
I'm developing a booking system in Rails 3.1. I have created a model for a Booking:
# == Schema Information
#
# Table name: bookings
#
# id :integer not null, primary key
# product_id :integer
# customer_id :integer
# booked_from :datetime
# booked_to :datetime
# paid :boolean
# payment_type :string(255)
# created_at :datetime
# updated_at :datetime
#
So what I want to do is to validate each entry and check whether the desired time period (booked_from - booked_to) is overlapping any period of another booking with the same product_id. The products also have an available_from and available_to field which it also has to validate against.
How do I do this?
Check if this works:
class Booking
validate :booking_period_not_overlapped
private
def booking_period_not_overlapped
unless Booking.where(
'(booked_from <= ? AND booked_to >= ?) OR (booked_from >= ? AND booked_from <= ?)',
booked_from, booked_from,
booked_from, booked_to
).empty?
errors.add(:booked_from, 'Invalid period.')
end
end
end
It just checks if there is any existing records whose booked_from and booked_to satisfy one of the following conditions (suppose your new booking is from 16:00 to 17:00):
it starts before the new booking, and not yet ended (e.g. 15:00 - 16:30 or 15:00 - 17:30)
it starts within the new booking period (e.g. 16:20 - 16:50 or 16:30 - 17:30)
I have a twitter-like feed in my web app. I've tried to optimize the query as much as possible but it still freezes when it's loading the "tweets" from PostgresSQL database.
Twitter, github, facebook, feeds are so smooth and fast.
What's the best way to accomplish that?
Regards.
**Edit: the code is the one in railstutorial.org.
http://railstutorial.org/chapters/following-users#sec:scopes_subselects_and_a_lambda
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
followed_ids = user.following.map(&:id).join(", ")
where("user_id IN (#{followed_ids}) OR user_id = :user_id",
{ :user_id => user })
end
# Table name: microposts
#
# id :integer not null, primary key
# content :string(255)
# user_id :integer
# created_at :datetime
# updated_at :datetime
#
Sorry for not providing enough information.**
Did you put any index on your tables?