I have three models:
class Update
attr_accessible :object_id, :object_type
belongs_to :object, :polymorphic
default_scope order('created_at DESC')
end
class Document
has_many :updates, as: :object
end
class Report
has_many :updates, as: :object
end
Now, using sql or the Active Record query interface I want to be able to grab pick out the lastest Update for each object.
I was thinking that I would do something like this:
Update.group(:object_id).group(:object_type)
That does indeed give me only one update per object, but it is not always the latest update and I am not sure why.
Does group just select a random record from the group? Is there a simple way to ensure that the latest update gets selected?
Related
I need to write a query in Rails that involves 3 different models. I need to know which Subscriptions are delivereable. But delivereable is not a column in Subscription but in BasePlan.
class BasePlan
has_many :plans
end
class Plan
has_many :subscriptions
end
class Subscription
belongs_to :plan
end
I've tried joining all three models together to no success:
Subscription.joins(:plans).joins(:base_plans).where(queried_column: true)
What would be the right way to write the query?
Subscription needs to know about it's relationship to :base_plans for your code to work.
class Subscription
belongs_to :plan
has_one :base_plan, through: :plan
end
Using #SteveTurczyn 's answer, you could query like this:
Subscription.joins(:base_plan).where(base_plans: { queried_column: true }). You need to first define the through relationship as Steve suggests, then you can traverse the relationship to get the column you need to check on BasePlan.
I'm trying to create an attribute in my select statement that depends on whether or not an association exists. I'm not sure if it's possible with a single query, and the goal is to not have to iterate a list afterward.
Here is the structure.
class Project < ApplicationRecord
has_many :subscriptions
has_many :users, through: :subscriptions
end
class User < ApplicationRecord
has_many :subscriptions
has_many :projects, through: :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :project
belongs_to :user
end
Knowing a project, the goal of the query is to return ALL users and include on them a new attribute call subscribed - denoting whether or not they are subscribed.
non-working code (pseudo code):
project = Project.find_by(name: 'has_subscribers')
query = 'users.*, (subscriptions.project_id = ?) AS subscribed'
users = User.includes(:subscriptions).select(query, project.id)
user.first.subscribed
# => true or false
I'm open to whether or not there is a better way of going about this. However, the information is:
You know the project record.
You query a list of ALL users
Each user record has a subscribed attribute, denoting whether its
subscribed to the given project
Solution:
I was able to figure out a straight forward solution using the bool_or aggregate method. Coalesce ensures that the value returned is false instead of nil, should no subscriptions exists.
query = "users.*, COALESCE(bool_or(subscriptions.project_id = '#{project_id}'::uuid), false) as subscribed"
User.left_outer_joins(:subscriptions)
.select(query)
.group('users.id')
Yep, you can do this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id])
Which will result in a SQL query like this:
SELECT *, "subscriptions"."project_id" FROM "users" INNER JOIN "subscriptions" ON "subscriptions"."user_ud" = "users"."id";
If you want to specify a specific project (i.e. use an expression), you can do it with Arel like this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42))
Unfortunately, you won't have a column name alias, and you can't call as on an Arel::Nodes::Equality instance. I don't know enough about the internals of Arel to have a way out of that box. But you can do this if you want the composability of Arel (e.g. if this is going to be something that needs to work with multiple models or columns):
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42).to_sql + " as has_project")
This is a bit clunky, but it works and provides a user.has_project method that returns a boolean. You can pretty it up like so:
class User
scope :with_project_status, lambda do |project_id|
has_project =
Subscription.arel_table[:project_id].
eq(project_id).to_sql + " as has_project"
joins(:projects).select(Arel.star, has_project)
end
end
User.with_project_status(42).where(active: true)
I am currently making a website that runs on Ruby on Rails. I am facing some issues while I was trying to join two tables, Rates and Locations, that I have with two different attributes name.
Rates: id rater_id rateable_id (and a few more attributes in this table)
Locations: id title body user_id (and a few more attributes in this table)
Here is the query that I am trying to do in SQL.
SELECT *
FROM rates, locations
WHERE rates.rater_id = locations.user_id AND rates.rateable_id = locations.id
I have read the official active record documents that provided by rubyonrails.org. I have tried doing these, but it does not work. Here is the code that I am trying to implant in app\controllers\users_controller.rb
#join_rating = Rate.joins(:locations).where("rates.rateable_id = locations.id AND rates.rater_id = locations.id")
#all_rating = #all_rating.where(rater_id: #user)
#count_all_rating = #all_rating.count
#join_rating, is trying to join the attributes with different names.
#all_rating, is trying to filter which location to show using the user ID
#join_rating, is trying to calculate the total numbers of locations that are rated by the user
Assume that everything is setup correctly and the only error is in the query that I am trying to do, how should I rewrite the statement so that I am able to show the locations that the user has rated using #all_rating.
Thank you!
A few points:
When in ActiveRecord you're starting a statement with the Rate class, it means the result is going to be a collection of Rate objects. So if you're trying to show locations, you should start with a Location class.
#locations_user_rated = Location.joins('INNER JOIN rates ON
rates.rateable_id = locations.id').where('rates.rater_id' => #user)
And if your ActiveRecord associations are well defined, you could simply do:
#locations_user_rated = Location.joins(:rates).where('rates.rater_id' => #user)
"Well defined" simply means you'll need to do something like the following. Note that I am not sure I understand your model relationships correctly. I assume below that every location has multiple rates, and that the reason your Rate model has the field called rateable_id instead of a location_id is because you want :rateable to be polymorphic. This means you probably also have a rateable_type field in rates table.
class Location < ActiveRecord::Base
has_many :rates, as: :rateable
end
class Rate < ActiveRecord::Base
belongs_to :rateable, polymorphic: true
end
If this polymorphism is not the case, things should actually be simpler, and I highly recommend that you follow Rails's conventions and simply name the relationship field location_id on your Rate model instead of rateable_id. Then you can do:
class Location < ActiveRecord::Base
has_many :rates
end
class Rate < ActiveRecord::Base
belongs_to :location
end
If still you are not convinced about the field name, you can customize things and do:
class Location < ActiveRecord::Base
has_many :rates, foreign_key: :rateable_id
end
class Rate < ActiveRecord::Base
belongs_to :location, foreign_key: :rateable_id
end
You can find more about how to customize associations here, and here.
I highly recommend taking advantage of ActiveRecord's has_many, belongs_to, and has_many through: functionality.
If you set up a model for each of these tables, with the correct relationships:
class User < ActiveRecord::Base
has_many :ratings, foreign_key: :rater_id
has_many :rated_locations, through: ratings, class_name: Location.name, source: :rater
end
class Rating < ActiveRecord::Base
belongs_to :rater, class_name: User.name
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :ratings
end
Then to access the locaitons that a user has rated, you just call
user.rated_locations
I have the following:
class OfferClaim
belongs_to :offer
end
class Offer
has_many :offer_claims
attr_accessible :claim_limit
end
And I need to be able to do something like this:
Offer.joins(:offer_claims).where("offer.claim_limit > offer_claims.count")
And only return the offers that have less associations then its limit.
This sounds like a perfect usage of counter_cache.
class OfferClaim
belongs_to :offer, :counter_cache => true
end
This would increment an offer_claim_count column on the offers table whenever a new OfferClaim is created (and decrement it when it's deleted).
Then your query would be:
Offer.where("offer.claim_limit > offer_claims_count")
No joins statement required.
I have tables 'orders' and 'items' with has_many association in the model.
class Order < ActiveRecord::Base
has_many :items
class Item < ActiveRecord::Base
belongs_to :order
Item consists of 'quantity' field, and Order consists of 'quantity_sum' field to track sum of associated items quantity.
For eg:
Order 1 : name='Toms shopping cart', quantity_sum=12
Item 1 : name='T-shirt', quantity=10
Itme 2 : name='Shoes', quantity=2
I have been looking for a way so that whenever new item is added/edited/deleted, the field 'quantity_sum' of Order gets updated automatically. Presently I have been using after_save method in Item, to update 'quantity_sum' field of Order.
Is there any other neat way of doing this besides 'after_save' ???
Similar to "counter_cache" for tracking count of associations, does rails have support for automatically keeping track of sum of some fields in the association?
Thanks
Remove the quantity_sum field from your table and add a quantity_sum method to the order class that sums up the quantity_values
class Order < ActiveRecord::Base
has_many :items
def quantity_sum
self.items.sum(:quantity)
end
end
Should do the trick. All you then need to do is remove any code you may have that updates the quantity_sum field. You will find that because the name of the method is the same as the field name (That you must not forget to delete) you won't have to refactor any of your code that makes use of it.
Obviously you need to be careful not to use this field unneccesarily like in a list of all orders in the system as this will be quite heavy on the database. O.K for a few hundred records but you'll notice a performance issue over thousands of orders.
Don't forget to remove that quantity_sum field from the order table
I think that this gem is what your'e looking for.
Look under "Totaling instead of counting" in the docs.
It should allow you to to something like this:
class Item < ActiveRecord::Base
belongs_to :order
counter_culture :order, :column_name => 'quantity_sum', :delta_column => 'quantity'
end
class Order < ActiveRecord::Base
has_many :items
end