I have a Ratings model with a phone_id attribute. Before creating a new ratings object I want to check if the phone_id is unique for the past week.
In my model I want to do something like this in the before_save callback:
self.all(:conditions => {:created_at => (1.week.ago..Date.today)}).include? self.phone_id
I would do it in clean sql (performance)
select count(phone_id) from ratings where created_at < DATE_SUB(NOW(), INTERVAL 1 MONTH)
You can use ActiveRecord validations with a constraint.
class Rating < ActiveRecord::Base
validates_uniqueness_of :phone_id, conditions: -> { where(:created_at => (1.week.ago..Date.today)) }
end
You can use validation on create:
class Rating < ActiveRecord::Base
validate :unique_phone_within_last_week?, on: :create
private
def unique_phone_within_last_week?
self.class.where("created_at > ?", 1.week.ago.to_date)
.where(phone_id: phone_id).empty? ||
errors.add(:phone_id, 'is not unique within last week') && false
end
end
In Rails 4 you can use validates_uniqueness_of with conditions proc:
class Rating < ActiveRecord::Base
validates_uniqueness_of :phone_id,
conditions: -> { where('created_at > ?', 1.week.ago.to_date) }
end
Read more about validates_uniqueness_of.
Checking SQL will be optimal:
SELECT COUNT(*) FROM "ratings"
WHERE "ratings"."phone_id" = 2 AND ("created_at" > '2014-01-14');
Note also that
Using interval 1.week.ago..Date.today is seems to be bad idea,
because records that were created today (in day of checking) are out of scope.
'2014-01-21 09:10:21' BETWEEN '2014-01-14 11:23:30' AND '2014-01-21' is false
Related
I have a model named Awardunit. Awardunit have many Awardleaders. One Award unit can have one or many Award leaders.
If I get all the record or search and get a collection of records to a variable named awardunits how can I count the number of Awardleaders in all the units in this collection?
Here's what I did :
#leaders = 0
#awardunits.each do |unit|
#leaders = #leaders + unit.awardleaders.size
end
Again to count the disabled leaders I use this :
#disabledleaders = 0
#awardunits.each do |unit|
#disabledleaders = #disabledleaders + unit.awardleaders.where(disabled: true).size
end
If I use this, it will have to go through all the records every time the page loads. Isn't there a better way of doing this?
You can make counting the associations cheap by adding a counter cache:
class AwardUnit << ActiveRecord::Base
has_many :award_units
end
class AwardLeader << ActiveRecord::Base
belongs_to :award_unit, counter_cache: true
end
Now add a new column called award_leaders_count to your AwardUnit table in a new migration:
def change
add_column :award_units, :award_leaders_count, :integer, default: 0
AwardUnit.all.each do |unit|
AwardUnit.reset_counters(unit.id, :award_leaders)
end
end
Rails will now automatically cache the number of award_leaders for every AwardUnit and #my_award_unit.award_leaders.count will give you the count without running another database query.
By default, Rails counter_cache only works for all award_leaders. If you need to count only those award_leaders that have a condition, you will have to add your own counter_cache:
class AwardUnit << ActiveRecord::Base
has_many :award_units
end
class AwardLeader << ActiveRecord::Base
belongs_to :award_unit
scope :disabled, -> { where(disabled: true) }
after_save :update_counter_cache
after_destroy :update_counter_cache
def update_counter_cache
award_unit.update_attribute(:disabled_award_units_count, award_unit.award_leaders.disabled.count)
end
end
migration:
def change
add_column :award_units, :disabled_award_leaders_count, :integer, default: 0
AwardUnit.all.each do |unit|
unit.update_attribute(:disabled_award_units_count, unit.award_leaders.disabled.count)
end
end
Now, when you have an array of AwardUnits, getting their combined count of disabled award leaders is as simple as
#award_units = AwardUnit.limit(5).to_a # or a similar query
#award_units.inject(0){|sum,unit| sum + unit.disabled_award_leaders_count }
You can eager load Awardleaders when fetching Awardunits, so you don't have to execute a count query for every Awardunit, like this:
#awardunits = Awardunit.includes(:awardleaders).where('awardleaders.disabled = ?', true) # the rest of the query
Or, you can query the count directly like this:
#leaders = Awardleader.where('awardunit_id IN (?)', #awardunits.map(&:id)).where(:disabled => true).count
This will run Database query only once
Awardunit.includes(:awardleaders).map {|award_unit| award_unit.awardleaders.size}.inject(0){|sum,item| sum + item }
in my rails 4 project, I have the following tables
In this SO question, I searched for a SQL query to fetch the projects with the actual project status id = XX. By actual, I mean the one with the max(created_at).
I got an answer for my query which is
select p.* from projects p
left join projects_status_projects psp on (psp.project_id = p.id)
where created_at = (select max(created_at) from projects_status_projects
where project_id = p.id)
and project_status_id = XX
My models are defined
class Project < ActiveRecord::Base
has_many :projects_status_projects
has_many :projects_statuses, :through => :projects_status_projects
end
class ProjectStatus < ActiveRecord::Base
has_many :projects_status_projects
has_many :projects, :through => :projects_status_projects
end
class ProjectsStatusType < ActiveRecord::Base
belongs_to :project
belongs_to :project_status
end
In my Project model, I have the following method
def self.with_status(status)
joins(:projects_status_projects)
.where('projects_status_projects.created_at = (select max(created_at) from
projects_status_projects where project_id = p.id)')
.where('projects_status_projects.project_status_id = ?', status)
end
While the query is correct, the received results are well filtered, I find this solution terrible and not elegant at all.
Is there any way to get the same result with scopes ?
Thanks for your help.
What do you think of
scope :with_status, -> (status) {
ProjectsStatusType.where(:project_status_id, status).order(:created_at).last.project
}
EDIT based on comments :
As sockmonk said, scopes should be chainable. Here is a cleaner way to do it, which also fix the problem if no project is found.
# ProjectStatusType model
scope :with_ordered_status, -> (status) {
where(:project_status_id, status).order(:created_at)
}
# Project model
def self.find_by_status(status)
project_statuses = ProjectsStatusType.with_ordered_status(status)
project_statuses.any? ? project_statuses.last.project : nil
end
how about?
scope :with_status, ->(status = "default_status") {
joins(:projects_status_projects).
where('projects_status_projects.project_status_id = ?', status).
order("projects_status_projects.created_at DESC").first
}
scope :with_status, ->(status = "default_status") {
joins(:projects_status_projects)
.where('projects_status_projects.project_status_id = ?', status)
.order("projects_status_projects.created_at DESC")
}
When you call it, you'll want to tack a '.first' to the end of it; can't include the .first in the scope itself, as that would make it unchainable.
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 )
")
I can think of a few ways to do this, but I'm unsure what to choose..
I have the class Topic and I am trying to scope this so that I only return Topics if it has the associated object Reply or topic.replies as a count greater than 0.
Worst way to do this :
#topics.select{ | topic | topic.replies > 0 && topic.title == "Conversation" }
Ideally, I'd like to use a where scope.
scope = current_user.topics
scope = scope.joins 'left outer join users on topics.registration_id = registration_members.registration_id'
# scope = .. here I want to exclude any of these topics that have both the title "Conversations" and replies that are not greater than 0
I need to "append" these selections to anything else already selected. So my selection shouldn't exclude all others to just this selection. It's just saying that any Topic with replies less than one and also called "Conversation" should be excluded from the final return.
Any ideas?
Update
Sort of a half-hashed idea :
items_table = Arel::Table.new(scope)
unstarted_conversations = scope.select{|a| a.title == "Conversation" && a.replies.count > 0}.map(&:id)
scope.where(items_table[:id].not_in unstarted_conversations)
You can use something called count cache, basically what it does is add a field to the table and store in that field the total of "associates" of the specified type and is automatically updated.
Checkout this old screen/ascii cast: http://railscasts.com/episodes/23-counter-cache-column?view=asciicast
Here is something newer: http://hiteshrawal.blogspot.com/2011/12/rails-counter-cache.html
In your case would be as follow:
# migration
class AddCounterCacheToTopìc < ActiveRecord::Migration
def self.up
add_column :topics, :replies_count, :integer, :default => 0
end
def self.down
remove_column :topics, :replies_count
end
end
# model
class Replay < ActiveRecord::Base
belongs_to :topic, :counter_cache => true
end
Hope it help you.
I have a Product model:
class Product < ActiveRecord::Base
belongs_to :subcategory
define_index do
# fields
indexes subcategory.name, :as => :subcategory, :sortable => true, :facet => true
# attributes
has subcategory_id, created_at, updated_at
#properties
set_property :delta => true
Now, suppose that a user updates a subcategory name, which is the proper way to update the products delta index?
According to this documentation: http://freelancing-god.github.com/ts/en/deltas.html, a save message should be sent to the product, so in this case I should go for each product related with the subcategory and send the save message, something like this:
class Subcategory < ActiveRecord::Base
has_many :products
after_save :set_product_delta_flag
private
def set_product_delta_flag
products.each { |product|
product.delta = true
product.save
}
end
end
I think that this is overkilling because we have like 100.000 products per subcategory.
Is this the correct way to update the delta index? Am I missing something?
After adding this:
def set_product_delta_flag
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
I'm always receiving this error:
NoMethodError (undefined method `index_delta' for #):
So, the solution to this problem was to send the message *define_indexes* to the Product model.
After fixing this issue, everything was ok, but the delta_index was not correctly updated, I needed to do save twice to the subcategory model.
So my final solution is this one:
after_commit :set_product_delta_flag
private
def set_product_delta_flag
Product.define_indexes
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
Using after_commit and define_indexes is the correct solution? Its the only one that I've found.
Try the following instead:
def set_product_delta_flag
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
A single SQL statement, a single delta re-indexing. Should perform far better :)