Reverse merge for an active record - sql

I'm facing an Rails (and finally a pur SQL) issue.
I have 3 tables (models). Event / User / Invitation
class Event < ApplicationRecord
has_many :invitations
end
class User < ApplicationRecord
has_many :invitations
has_many :events, through: :invitations
end
class Invitation < ApplicationRecord
belongs_to :event
belongs_to :user
end
I want to list all events where a specific user does not have invitation.
Contraints (very important in my case):
I'm starting my request by Event.
Basically, I would say it's the opposite of a merge, like a merge.not(user.events).
The only solution I found is:
Event.where.not(id: user.events.pluck(:id))
But obviously, I don't like it. 2 queries that might be somehow merge into a single one.
Any idea?

use select instead of pluck, it will create sub-query instead pulling records from database. Rails ActiveRecord Subqueries
Event.where.not(id: user.events.select(:id))

Related

Conditionally Saving has_many_through Relationships

In Rails, how would one conditionally associated records on a has_many_through relationship? Using the following Rails docs example:
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
Suppose I wanted to have an appointment reference exactly two physicians. That is, there will not be any appointment record one there is less than two physicians assigned. However, how could that appointment then reference each physician?
Example
Basically, I want to keep track of users liking other users and mutual likes between them. A connection is established when both users like each other. But I don't want a connection when only one user likes another but it is not reciprocal.
When User A likes User B. A "like" is created.
When User B likes User A. A "like" is created. A "connection" is also created.
The connection should be able to call:
connection.users
The user should be able to call:
user.likes
user.connections
The problem that I'm having is how can that relationship table know when it is mutual?
For the original question, a connection doesnt make a difference between the two users, so i would model it as a one to many relationship and validate it only has two users.
A like has two users, the liker (giver of the like) and the likee (receiver of the like). Every time you create new like, you should check if the likee also likes the liker. If likee.likes.where(likee: liker)? If yes, then create the new connection with both users.
class User < ApplicationRecord
has_many :likes
has_many :connections
end
class Like < ApplicationRecord
belongs_to :user, :foreign_key => 'liker_id'
belongs_to :user, :foreign_key => 'likee_id'
end
class Connection < ApplicationRecord
has_many :likes
has_many :users, through: likes
end
I want to add that i am not 100% sure of this as i am currently learning Rails myself. But this is what I came up with and hopefully its useful (and correct).

Deep model associations with Rails

Let's imagine that I have a CPA tracking system.
I would have following models: an Offer, it has some Landings, each of them has multiple Links, each of the links has a bunch of Visits.
So, I what I want is DRY code, therefore offer_id column within visits table is unacceptable. The workaround here is delegated methods like this:
class Offer < ActiveRecord::Base
has_many :landings
has_many :links, through: :landings
has_many :visits, through: :landings
end
class Landing < ActiveRecord::Base
belongs_to :offer
has_many :links
has_many :visits, through: :links
end
class Link < ActiveRecord::Base
belongs_to :landing
has_many :visits
delegate :offer, to: :landing
end
class Visit < ActiveRecord::Base
belongs_to :link
delegate :landing, to: :link
delegate :offer, to: :link
end
It works nice with a single visit, e.g. visit.offer.id. But what if I need different visits associated with one offer?
The issue is that I'm unable to construct a valid query using ActiveRecord API. It might look like Visits.where(offer: Offer.first), but it doesn't work this way, saying ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: visits.offer: SELECT "visits".* FROM "visits" WHERE "visits"."offer" = 1, which is predictable.
Question: How should I organize my code to make statements like Visits.where(offer: Offer.first) work efficiently without duplicating offer_id column within visits table?
You code was organized nicely, don't need to refactor I think. You can achieve that by defining a scope in Visit like this:
class Visit < ActiveRecord::Base
scope :from_offer, -> (offer) {
joins(link: :landing).where(ladings: {offer_id: offer.id})
}
scope :from_landing, -> (landing) {
joins(:link).where(links: {landing_id: landing.id})
}
end
So the query will be:
Visit.from_offer(Offer.first)

Rails4 query help, find unique records with has_many though and a joining model

I have the following table structure
manufacturers --> products ---> available_sizes_products <-- sizes
and the following models
class Manufacturer < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
has_many :sizes, :through => :available_sizes_products
has_many :available_sizes_products
end
class AvailableProductSize < ActiveRecord::Base
belongs_to :sizes
belongs_to :products
end
class Size < ActiveRecord::Base
has_many :products, :through => :available_sizes_products
has_many :available_sizes_products
end
I need to get a unique list of manufacturers, that have products in size "XL" or "L" for example.I'm getting lost in the chaining of joins etc.
class Manufacturer < ActiveRecord::Base
def self.with_sizes(sizes=[])
#sizes = Sizes.find(sizes)
...
end
end
Can someone help me with that ? Trying to do the Rails 4 way rather than drop down to SQL, since I need the query to run on several DBS
Thanks
First of all you have to use single form of noun in belongs_to expression.
And for the query try this one:
Manufacturer.includes(:products).where(products: (size: "XL"))
I use "includes" to avoid N+1 query. Otherwise it will send two queries: one for Manufacturers and one for products. Write back, if this one doesn't fit your need.
EDIT
BTW, if you want to use exactly joining, write joins instead of includes.
Everything is here:
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
and here:
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
After going through the docs for joins
this is what worked :
Manufacturer.joins(products: :sizes).where(sizes: {id:ids}).distinct
This Rails way returns the model correctly.

Rails Dependent Update On Associated Record

Given two associated models in rails (4.0),
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
The above code will ensure that when an instance of User is destroyed, its associated record will be, too.
So far so good.
My question is, is it possible to equally easily invoke a dependent update as well, so that every time User is updated, Subscription will be updated as well?
This could look like this:
class User < ActiveRecord::Base
has_one :subscription, dependent: [:update, :destroy]
end
So that when User gets updated successfully, the associated Subscription will re-save, thus invoking its update filters (i.e. before_save, before_update, after_save, after_update).
Is there an elegant way to do this? If not, what is the closest way to cleanly get to this?
Thank you!
Try this,
has_one :subscription, :dependent => destroy, :autosave => true
For more details see the documentation http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

Querying for rows without matching ID in associated table

I have a very standard app backed by an SQL database with a User model, a Problem model, and a CompletedProblem model acting as a join table between the two.
I'm trying to create a method that returns all problems not solved by a particular user. I have run into a wall, however, and I would appreciate pointers on what my method should look like.
Below are the models as well as my latest (incorrect) pass at creating this method.
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
def unsolved_problems
Problem.includes({:wall => :gym}, :completed_problems).
where('completed_problems.user_id != ? OR completed_problems.user_id IS NULL)', self.id)
end
end
class Problem < ActiveRecord::Base
has_many :completed_problems
has_many :users, :through => :completed_problems
end
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
end
(For the curious: this method does work so long as there is only one user marking problems as solved. As soon as you add a second, each user starts to return only those problems that have been solved by other users, instead of those not solved by herself.)
Via a friend:
select * from problems where id not in (select problem_id from completed_problems where user_id = USER_ID))
Although I'd still be interested in hearing if there's a way in ActiveRecord to do this.
I think something like this will do it:
Problem.where(["id NOT IN (?)", self.problems.all.map(&:id)])