简体   繁体   中英

Create Rails scope for associated model

I have 2 models:

DeliverySlot has_many :orders Order belongs_to :delivery_slot

Delivery Slots have a limit of how many orders they can hold. I want to create a scope to give all the available delivery slots. An available delivery slot is one that hasn't reached it's limit of associated orders.

My attempt looks like:

scope :available, where("limit > ?", order.count).joins(:orders)

order.count is pseudocode above.

To do this like you have setup you would need to use orders.count instead of order.count because you're referring to the association. This would prompt ActiveRecord to assemble a query that looks something like SELECT COUNT(*) FROM orders WHERE delivery_slot_id = 1 .

Rails is actually smart enough to then use that as a subquery in your where condition when you pass it appropriately, a la where('limit > ', orders.count) . But as you might see, this won't work if it's precompiled because the query uses an explicit ID in the condition.

What you need instead is to count orders with an ambiguous condition, then use it as a subquery: where('limit > ?', Order.where(delivery_slot_id: 'delivery_slots.id').count) . If you tried to run the query for the order count on its own it would fail on delivery_slots , but because it's in the subquery here you should be smooth sailing.

I'd like to propose another way of doing this altogether though, using a counter cache:

class AddCounterCacheToDeliverySlots < ActiveRecord::Migration
  class DeliverySlot < ActiveRecord::Base; end
  def change
    add_column :delivery_slots, :orders_count, :integer, default: 0
    add_index :delivery_slots, [:orders_count, :limit]

    DeliverySlot.reset_column_information
    DeliverySlot.find_each do |delivery_slot|
      DeliverySlot.update_counters delivery_slot.id, orders_count: delivery_slot.orders.count
    end
  end
end

class Order < ActiveRecord::Base
  belongs_to :delivery_slot, counter_cache: true
end

class DeliverySlot < ActiveRecord::Base
  has_many orders
  scope :available, where('orders_count < limit')
end

Rails will automatically increment and decrement the orders_count column for each DeliverySlot , and because it's indexed, it's ridiculously fast to query.

scope :available, lambda { 
  |delivery_slot| joins(:orders).
         where("limit > ?", order.count) 
}

try this

So I found a way to do it in SQL. If anyone knows of a more ruby way without creating loads of database queries please jump in.

scope :available, joins('LEFT JOIN orders 
    ON orders.delivery_slot_id = delivery_slots.id')
    .where("delivery_slots.limit > ( 
        SELECT COUNT(*) FROM orders 
        WHERE orders.delivery_slot_id = delivery_slots.id )
    ")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM