get all elements with througth relation rails? - ruby-on-rails-4

I have following models :
class Topic < ActiveRecord::Base
has_many :topic_tags
has_many :tags, through: :topic_tags
end
class Tag < ActiveRecord::Base
STATUS_DISABLED = 0
STATUS_ENABLED = 1
has_many :topic_tags
has_many :topics, through: :topic_tags
end
I whant know how get all topics for all tags where status is STATUS_ENABLED.
I would like something like :
Topic.where(tags: {status: Tag::STATUS_ENABLED)
How the best way to do that?
EDIT:
I found fastidious solution:
Tag.includes(:topics).where(status: Tag::STATUS_ENABLED).map(&:topics).flatten
better way?

This should work:
Topic.joins(:tags).where(tags: {status: Tag::STATUS_ENABLED}).group("topics.id")
That applies an inner join. And it's much better than your tags.map(&:topics).flatten solution. The map + flatten queries the database once per enabled tag. This way it's only 1 query.

Related

Ruby on Rails Unique Ordered Query

What I am trying to do is:
Find the most recent CompetencyLog (completed_at) for each Competency on a Course for a Member
class Member < ActiveRecord::Base
has_many :competency_logs
has_many :awards
end
class CompetencyLog < ActiveRecord::Base
belongs_to :member
belongs_to :competency
has_one :course, through: :competency
end
def Course < ActiveRecord::Base
has_many :competencies
has_many :awards
end
class Competency < ActiveRecord::Base
belongs_to :course
end
I have managed to get the ordered list
course = Course.find(params[:course_id])
current_member.competency_logs.where('competency_id IN (?)', course.competency_ids).ordered
From here I have tried a few different things with limited to no success. Any recommendations would be greatly appreciated. Looking to do as much of this in the database as possible for speed since this is frequently called and depended on ever changing timestamps on the CompetencyLog
The results I want are basically
member.competency_logs.where('competency_id IN (?)', course.competency_ids).uniq.pluck(:competency_id)
But instead of the competency_id I want the competency_log models
So I've added some additional relationships and come up with the following so far, currently investigating postgresql DISTINCT ON as an alternative
competency_logs = competencies.collect { |c| c.competency_logs.ordered.first }
competency_logs.collect { |c| c.current? }.all?
The following will work:
max_completed = current_member.competency_logs.where('competency_id IN (?)', course.competency_ids).maximum(:completed_at)
current_member.competency_logs.first(:conditions => {:completed_at => max_completed})
This might solve your problem. It does however operate under the assumption that you don't have duplicated completed_at timestamps per member.
some_member.competency_logs.
where(
competency_id: some_course.competency_ids,
completed_at: some_member.competency_logs.
select("MAX(completed_at) as completed_at").
where(competency_id: some_course.competency_ids).
group(:completed_at)
)

has_many multiple associations with different names

Ruby 2.1.0/Rails 4.0.2
I have a Bus model and a Cluster model. They look as follows:
class Bus < ActiveRecord::Base
has_many :left_centroids, class_name: "Centroid"
has_many :right_centroids, class_name: "Centroid"
end
class Centroid < ActiveRecord::Base
belongs_to :bus
end
Bus also has a method that is basically KMeans Algorithm, so I run it to replace the old left_centroids and then to replace the right_centroids. Right and Left differ in the value of a given field from the Centroid Model.
I have tried saving those via simple setting: #bus.left_centroids = centroids_for_algorithms and also through update_attributes. But whenever I save one, say left, right is overwritten with the values of left, and the other way around, which is pointless in the context of my application.
Am I missing something?
UPDATE: After I run the K Means Algorithm (from the ai4r gem, link in comments), I collect the centroids
def algorithm(direction)
clusters = k_means_algorithm_part
centroids_to_add_to_bus = Centroid.limit 0
clusters.centroids.each do |centroid|
cent = Centroid.create(
:latitude => centroid[0].to_d,
:longitude => centroid[1].to_d,
:catch_time => centroid[2],
:direction => direction,
:bus_id => bus_id
)
centroids_to_add_to_bus.append cent
end
bus = Bus.find(bus_id)
if direction
bus.right_centroids = centroids_to_add_to_bus
else
bus.left_centroids = centroids_to_add_to_bus
end
end
Can you add another column in the centroids table called type. Now you can use this to apply conditions on your associations. Like this
class Bus < ActiveRecord::Base
has_many :left_centroids, class_name: "Centroid", -> { where type: 'left_centroid' }
has_many :right_centroids, class_name: "Centroid", -> { where type: 'right_centroid' }
end
Not sure of the syntax, but I think this should help.

union between two has_many associations

I use rails 4.1 with Postgres and I have a model like this one:
class User < ActiveRecord::Base
has_many :events
has_many :event_memberships
has_many :shared_events, through: :event_memberships, source: event
def all_events
#... code goes here
end
end
I'd like to do an union between events and shared_events associations but I can't make it work.
The only thing I can make it work is this:
def all_events
events + shared_events
end
But it will execute 2 SQL queries instead of one and I won't be able to order or limit the results.
I have also tried something like this:
def all_events
User.find_by_sql("(#{events.to_sql}) UNION (#{shared_events.to_sql})")
end
Or this method https://coderwall.com/p/9hohaa
But both methods throw this error:
PG::UndefinedParameter: ERROR: there is no parameter $1
That seems to be known bug in rails 4+ and it won't be resolved https://github.com/rails/rails/issues/13686
So my only option here seems to write plain old SQL.
Anyone with a better idea ?

Using CanCan with Polymorphic Associations

I am struggling to use CanCan with my polymorphic associations. It is breaking down when I try and load the polymorphic objects in my controller.
class Trip < ActiveRecord::Base
has_many :images, as: :viewable
end
class Image < ActiveRecord::Base
belongs_to :viewable, polymorphic: true
end
Now I have a controller that deals specifically with images assigned to a trip.
class TripImagesController < ApplicationController
load_and_authorize_resource :trip
load_and_authorize_resource :image, through: :trip
def index
end
end
However, when I hit this index action I get the following error:
Couldn't find Image with id= [WHERE "images"."viewable_id" = $1 AND "images"."viewable_type" = $2]
I can see the query being executed in the Rails logs:
SELECT "images".* FROM "images" WHERE "images"."viewable_id" = $1 AND "images"."viewable_type" = $2 AND "images"."id" = $3 LIMIT 1 [["viewable_id", 1], ["viewable_type", "Trip"], ["id", ""]]
So the select statement looks good, except that it's trying to find only a single image (even though I'm in an index action). It's specifying an image id (which obviously does not exist in the route params) and it's also limiting the result to a single row.
This is using Rails 4.0.2 and CanCan 1.6.10. What am I missing here?

ActiveRecord query through multiple joins

I have a schema like this.
managers
has_many :emails
has_many :stores
emails
belongs_to :manager
stores
belongs_to :manager
belongs_to :region
regions
has_many :stores
has_many :readings
readings
belongs_to :regions
I want to get readings for a manager. In SQL I would do something like this.
SELECT * FROM managers
JOIN stores ON stores.manager_id = managers.id
JOIN regions ON stores.region_id = regions.id
JOIN readings ON readings.region_number = regions.number
WHERE
manager.name = 'John Smith'
AND
regions.number = '1234567'
LIMIT 100
I can't figure out how to do this in activerecord. I have been trying to make sense of http://guides.rubyonrails.org/active_record_querying.html and http://guides.rubyonrails.org/association_basics.html but it's not sinking in. I think I just need to see it from a different view point.
I was thinking I would be accessing the data like this but I think I just don't understand how it works.
managers.name
managers.stores.name
managers.stores.regions.readings.limit(10)
I have been having to so something like this which is a whole lot uglier.
managers.first.stores.first.regions.first.readings.limit(10)
Consider the following models (and use of has_many through) :
class Reading < ActiveRecord::Base
belongs_to :region,
inverse_of: :readings
end
class Region < ActiveRecord::Base
has_many :readings,
inverse_of: :region
has_many :stores,
inverse_of: :region
end
class Store < ActiveRecord::Base
belongs_to :region,
inverse_of: :stores
belongs_to :manager,
inverse_of: :stores
end
class Manager < ActiveRecord::Base
has_many :stores,
inverse_of: :region
has_many :emails,
inverse_of: :manager
has_many :regions,
through: :stores
has_many :readings,
through: :regions
end
class Email < ActiveRecord::Base
belongs_to :manager,
inverse_of: :emails
end
Now your question is a little ambiguous because you say you want to obtain readings for a manager but your SQL doesn't select readings at all and also prescribes a region.
Assuming you want all Reading's matching a given Manager and Region:
#readings = Reading.joins(region: { stores: :manager }).where(
manager: { name: 'John Smith' },
region: { id: 1234567 })
Assuming you also want to eager load regions, stores and managers to avoid 1+N queries:
#readings = Reading.includes(region: { stores: :manager }).where(
manager: { name: 'John Smith' },
region: { id: 1234567 })
Assuming you have a managers name and want both their details and readings:
#manager = Manager.where(name: 'John Smith').first!
#readings = manager.readings
All of the above query examples return ActiveRecord::Relation's which can be further chained with where conditions, or joins, limit, group, having and order etc
You should also consider the differences of joins, includes, preload, eager_load and references methods. There is a brief on them here I would alos encourage you to read docs, guides and blogs about Arel as it supports joins and aliasing too.
After using ActiveRecord in anger for a while now I have come to the conclusion that Sequel/SequelModel is a much better DB/ORM than ActiveRecord. No disrespect to the developers but I've found Sequel is a better tool. Arel has thin documentation for years now and ActiveRecord/Arel have failings in a number of areas such as join conditions, control of join types and eager loading, unions, intersections, recursive queries, trees/adjacency lists, and many other SQL features that Sequel covers.
Since you appear to be just starting out with AR you may wish to instead start out with Sequel than struggle with weaknesses and the frustrations of ActiveRecord querying including the disjointed use of AR and Arel, Relations vs Associations and query composition oddities, it goes on and on. There is nothing more frustrating than knowing the SQL you want but ActiveRecord/Arel conspire to stop you so you're forced to use the touted escape route and 'just use SQL string fragment' and you get back a result that can't be chained, but the rest of your code expects a Relation! (eg paged results)
I wanted to comment on the comment from user1757006 Oct 4 '13 at 14:39, but I don't have enough points..
Anyway, this addresses deeper nesting scenarios. I have added planet and country models to show how the syntax works when needing to chain additional models.
#readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },
region: {id: 1234567 })
Assuming managers is a model name
managers.find(:all,
:joins=>" JOIN stores ON stores.manager_id = managers.id
JOIN regions ON stores.region_id = regions.id
JOIN readings ON readings.region_number = regions.number"
:conditions=>"manager.name = 'John Smith' AND regions.number = '1234567'"
:limit=>100)
try something like this:
managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100)