Retrieve as Model in Active Record - sql

Is it possible to take the results of any arbitrary query and cast them to any ActiveRecord::Base model, without using Base.connection.execute?
For example, given these models:
class Foo < ActiveRecord::Base
has_and_belongs_to_many :bars
end
class Bar < ActiveRecord::Base
has_and_belongs_to_many :foos
end
If we run the a query like this, which loops back, we are stuck with Bar objects:
Foo.first
.bars.joins(:foos) # => ActiveRecord::Relation [Bar, Bar, Bar...]
How can the query be made to return an ActiveRecord::Relation [Foo, Foo, Foo...]?

Give this a shot
Foo.joins(:bars).merge(Foo.first.bars).uniq

Related

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)

Find records through two intermediate models in rails?

I'm having some trouble trying to fetch some models via SQL in rails and I was wondering if anyone knows of a good solution for this particular problem. Basically, these are what my classes look like:
class SubscriberList < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
has_many :messages
belongs_to :subscription_list
end
class Announcement < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :subscription
belongs_to :announcement
end
Now, I want to access all the Announcements for a SubscriptionList excluding duplicates. Is this possible? Can it be done with a single SQL query or is that just wishful thinking?
For example here's what I want:
class SubscriptionList < ActiveRecord::Base
def announcements
Announcements.joins(:messages).where(message: { subscription: {subscription_list: self} })
end
end
I think your idea is correct in general. Try this variant
Announcements.
joins(messages: {subscription: :subscription_list}).
where(subscription_lists: {id: self.id})
# opposite
SubscriptionList.
joins(subscriptions: {messages: :announcement}).
where(announcements: {id: self.id})
Notes:
* these queries may return duplicates - so uniq can be added to them
* self can be omitted (I wrote it to show that this is id of instance and avoid missunderstanding)

Efficient way to return select columns from Rails 4 ActiveRecord nested query

In my Rails 4 app, I have the following models:
class Person < ActiveRecord::Base
has_many :addresses
end
class Address < ActiveRecord::Base
belongs_to :person
belongs_to :city
end
class City < ActiveRecord::Base
has_many :addresses
end
I'm using the :includes function to return query result into one variable:
Address.includes(:person, :city).where("person_id = 1")
It works as expected, except that I do not want the query to return every single column.
Here's what I've tried:
use select and specify table name and column names explicitly, e.g. "city.name", but Rails generates a big query with outer joins, that can be very costly, especially when there are lots of concurrent requests, so prefer a better solution.
don't want to hard code complete and raw SQL statements, because of maintenance issue later on
create a new "dummy" belongs_to relationship like in Address: belongs_to :city_select_columns, -> { select('name') }, :class => 'City', but that doesn't work (actually I'm not sure if that select is even supported, only came across documentation about where so far).
maybe define scope in City and Person? but I'm not sure how it should be defined or if it'd make sense to do it this way
Suggestions? Thanks
Have you tried this?
class Person < ActiveRecord::Base
has_many :addresses
has_many :cities, :through => :addresses
end
class Address < ActiveRecord::Base
belongs_to :person
belongs_to :city
end
class City < ActiveRecord::Base
has_many :addresses
end
Then:
Person.find(1).cities.pluck(:name)
Looks like this generates an INNER JOIN but with indexes it shouldn't be too costly?
Did you try select?
Address.select(<output_columns>).includes(:person, :city).where("person_id = 1")
Could not find a good query method using Rails' API, I ended up writing a raw inner join SQL, then call ActiveRecord::Base.connection.execute to run it.

Create a feed with acts_as_follower

I have three models
class Place < ActiveRecord::Base
acts_as_followable
has_many :events
end
class Event < ActiveRecord::Base
attr_accessible :startdate
acts_as_followable
belongs_to :place
belongs_to :user
end
class User < ActiveRecord::Base
acts_as_followable
acts_as_follower
has_many :events
end
I need to do two things:
get a homogeneous sorted list of events for followed places or users
get a homogeneous sorted list of events for followed places and users and events without duplicates
So for instance
User.find(1).following_places.includes(:events).collect{|p| p.events}.flatten.sort{|a,b| a.startdate <=> b.startdate}
should return a list of events for all of the places a user is following, sorted by date. The issue with this, if I am understanding correctly, is that the collect, flatten, and sort occur in ruby instead of SQL.
The second part should look something like the following, and ideally occur entirely in SQL as well:
user = User.find(1)
(user..following_places.includes(:events).collect{|p| p.events}.flatten +
user.following_users.includes(:events).collect{|p| p.events}.flatten +
user.following_events).sort{|a,b| a.startdate <=> b.startdate}
This answer is close, but not quite what I am looking for.
Thanks for your help!
Have you tried using order?
User.find(1).following_places.includes(:events).order('events.startdate ASC')

Custom Association Method - Can This Be Done

I have three models: Wager, Race, RaceCard and WagerType. I've created a has_many association in Wagers and have added a custom association method (in_wager). The purposes of the method is to filter the correct races for each wager. (Some wagers span multiple races). I'd like to be able to do somethings like: Wager.first.races.in_wager and have the appropriate races returned.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
My custom method works fine if I hardcode the values for a and b, however, I need those values to be dynamic. Specifically, the value of b should equal the race_nbr attribute from the Wager model:
b = wagers.race_nbr
and the value of a should equal b minus the Number Of Race for the particular Wager type (know as Legs) plus 1:
a = b - Legs + 1
The value for legs is in the WagerType model. Note Wagers belong_to WagerType and WagerType has_many Wagers. Therefore, a could be expressed as:
a = (b - (select wager_types.legs where wagers_types.id = wagers.wager_type_id) + 1)
MY Question: Is it actually possible to do this with my in_wager association method. I've been banging my head on this for a couple of evening now and can't quite figure out how to assign the correct values to a and b. Also if you feel I'm coming at this the wrong way, I'd be happy to hear alternative approaches. Thanks for your help.
Note: I never really mentioned the RaceCard or Races models. They have the following associations:
class RaceCard < ActiveRecord::Base
has_many :races
end
class Races < ActiveRecord::Base
belongs_to :race_card
has_many :wagers, :through => :race_card
end
Update: I was reading Design Patterns in Ruby last night and came across the Proc. I'm going to see if I can use it within the Association method to calculate the values for a and b.
you can use self.proxy_association.owner to get the parent object inside of an association method. From there you can get the values you want.
If I understand your models correctly then the code should look something like this.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
owner = self.proxy_association.owner
b = owner.race_nbr
a = b - owner.wager_type.legs + 1
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
The I got this from the Rails api reference to Association Extensions (The reference to proxy_association is at the bottom of the section).
Do you absolutely need to use an has_many relation ? maybe you could just create a method in the Wager class
def races
b = self.race_nbr
a = b + self.race_card.legs
Races.find_by_race_card_id(self.id).where('race_nbr BETWEEN ? AND ?', a, b)
end
I don't really understand your model, so the request is certainly wrong, but you get the idea...