where to put code that effects multiple models - ruby-on-rails-3

Every day at, say 5pm I want to generate an Invoice model for all new Order models which have been generated in the past 24 hours.
What are some options for where this method should reside?
Are there any problems with having this as a method inside the Order model itself? e.g.
class Order < ActiveRecord::Base
def generate_invoice
invoice = Invoice.new
...
return invoice
end
end

I think you should create a module and include that in your models
Ex:
module Invoice
def generate_invoice
invoice = Invoice.new
...
return invoice
end
end
and in your models
class Order < ActiveRecord::Base
include Invoice
end
HTH

Related

Rails 4 - query on association count is greater than an attribute

I want to do something like "Find all Books where book.pages.count < books.max_pages".
So the models are:
class Book
has_many :pages
end
class Page
belongs_to :book
end
I know I can find books w/ a set number of pages. eg:
# Get books w/ < 5 pages.
Book.joins(:pages).group("books.id").having("count(pages.id) < ?", 5)
Is there a good way to do this with a dynamic page count? eg:
Book.joins(:pages).group("books.id").select(.having("count(pages.id) <= book.max_pages")
If not I can always just store something inside the Book model (eg book.is_full = false until a save causes it to be full), but this is a bit less flexible if max_pages gets updated.
You could create a scope like this:
def self.page_count_under(amount)
joins(:pages)
.group('books.id')
.having('COUNT(pages.id) < ?', amount)
end
UPDATE
This should work if max_pages is an attribute of the Book model.
def self.page_count_under_max
joins(:pages)
.group('books.id')
.having('COUNT(pages.id) < books.max_pages')
end
Use counter_cache!
http://guides.rubyonrails.org/association_basics.html 4.1.2.3 :counter_cache

Get records based on conditions on a set of related records

I have two classes
class Patient < ActiveRecord::Base
belongs_to :camp
has_many :vaccinations
end
class Vaccination < ActiveRecord::Base
belongs_to :patient
end
Each vaccination has members called vaccine_id. A 'complete' vaccination consists of a set of (say) 6 different vaccines, with :names = {A,B,C,D,E,F}.
Patients receive many vaccinations, and I want a report of all the patients who received 'complete' vaccinations. Is there a SQL or ActiveRecord query I can use to get this list of patients?
Perhaps something like:
# in patient.rb
scope :with_completed_vaccinations, ->(number) {
joins(:vaccinations).group('patients.id').
having('COUNT(vaccinations.name) >= ?', number)
}
With this scope you are able to write queries like:
Patient.with_completed_vaccinations(6)
This is an easy way to get all patients who have received complete at least one complete vaccination:
Patient.select { |pat| pat.vaccinations.any? { |vac| vac.complete? } }
And for patients where all vaccinations are complete, just switch the .any? with a .all?
And you could wrap this in a scope, for example by putting this method in your patient.rb
def self.with_complete_vaccinations
select { |pat| pat.vaccinations.any? { |vac| vac.complete? } }
end
This all assumes you have a complete? method defined for your vaccination model.

Resume of product query

I have the following schema table:
I have three activerecord models with their associations. I am struggling with a query which will show the following information for each product:
Product Name, Money Total, Quantity Sold Total
It should also take account on the status of the order that the product_line are associated with, which it should be equal to "successful".
I also want a second one query which it will show the above but it will have restriction based on the month (based on the orders.created_at column). For example if I want the sales for January of this product.
Product Name, Total Money so far, Quantity total, Month
I managed to create something but I think it isn't very optimized and I used ruby's group_by which it is doing many additional queries on the view. I would appreciate how you usually start thinking about creating a query like that.
Update
I think I almost managed to solve the first query and it is the following:
products = Product.joins(:product_lines).select("products.name, SUM(product_lines.quantity) as sum_amount, SUM(product_lines.quantity*products.price) as money_total"),group("products.id")
I tried to split each columns separately and find out how I could calculate it. I haven't take into account the order status though.
The associations are the following:
ProbudtLine
class ProductLine < ActiveRecord::Base
belongs_to :order
belongs_to :cart
belongs_to :product
end
Product
class Product < ActiveRecord::Base
has_many :product_lines
end
Order
class Order < ActiveRecord::Base
has_many :product_lines, dependent: :destroy
end
I finally did it.
First query:
#best_products_so_far = Product.joins(product_lines: :order)
.select("products.*, SUM(product_lines.quantity) as amount_total, SUM(product_lines.quantity*products.price) as money_total")
.where("orders.status = 'successful'")
.group("products.id")
Second query:
#best_products_this_month = Product.joins(product_lines: :order)
.select("products.*, SUM(product_lines.quantity) as amount_total, SUM(product_lines.quantity*products.price) as money_total")
.where("orders.status = 'successful'")
.where("extract(month from orders.completed_at) = ?", Date.today.strftime("%m"))
.group("products.id")

Alternative to a union with ActiveRecord

I think I want to do a union in Rails, but according to this post rails union hack, how to pull two different queries together unions aren't natively supported in Rails. I'm wondering if there is a better way to approach this problem.
I have table of items, each item has many prices, but I only want to join one price to each item.
To determine the proper price for an item I have two additional foreign keys in the price model: category_id and discount_id. Each could independently declare a price for an item.
Ex.
Item + Category = Price1 and Item + Discount = Price 2
If discount_id matches a passed id I want to exclude the price results FOR THAT ITEM ONLY that match Item + Category. Also I'm trying not to loose lazy loading.
Hopefully the problem is clear! If not I'll try to clarify more, thanks in advance.
Your models would start off looking something like this:
class Price < ActiveRecord::Base
belongs_to :item
belongs_to :category
belongs_to :discount
scope :category, where("prices.category_id IS NOT NULL")
scope :discount, where("prices.discount_id IS NOT NULL")
end
class Item < ActiveRecord::Base
has_many :prices
end
class Category < ActiveRecord::Base
has_many :prices
end
class Discount < ActiveRecord::Base
has_many :prices
end
One way of doing this is to add a class method to Price that encapsulates this logic:
class Price < ActiveRecord::Base
def self.used
discount_items_sql = self.discount.select("prices.item_id").to_sql
where("prices.discount_id IS NOT NULL OR prices.item_id NOT IN (#{discount_items_sql})")
end
end
This is effectively the same as this query:
SELECT * FROM prices
WHERE prices.discount_id IS NOT NULL -- the discount_id is present on this record,
OR prices.item_id NOT IN ( -- or no discount_id is present for this item
SELECT item_id FROM prices WHERE discount_id IS NOT NULL)
You can add these helper methods on your Item model for simplicity:
class Item < ActiveRecord::Base
def category_price
prices.category.first
end
def discount_price
prices.discount.first
end
def used_price
prices.used.first
end
end
Now you can easily get each individual 'type' of price for a single item (will be nil for prices that aren't available):
item.category_price
item.discount_price
item.used_price

How do I Create a Rails Model from a Subset of Table Records

I am trying to create several models that all pull from the same table. How do I limit the table records in each model? And before you tell me to change my data structure, this is a reporting application that is pulling from a preexisting backing DB over which I have no control.
My table looks something like:
Vehicle_Table
id vehicle_type name
--------------------
1 Car Foo
2 Car Bar
3 Motorcycle Baz
4 Car Barf
And I want to build models for Car and Motorcycle like:
class Car < ActiveRecord::Base
set_table_name 'Vehicle_Table'
end
and
class Motorcycle < ActiveRecord::Base
set_table_name 'Vehicle_Table'
end
But I have no idea how to say, "Hey Active Record, I only want records where vehicle_type = motorcycle in the motorcycle model."
I'm sure this is friggin' obvious, but all of my Google searches return ways to FIND subsets in a model rather than RESTRICT a model to a specific subset of records.
This is called Single Table Inheritance (STI).
If you had a column named type in your table, it would likely work automatically. But you can change this column name that Rails uses to tell types apart.
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
Single table inheritance
Active Record allows inheritance by storing the name of the class in a column that by default is named “type” (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.where(:name => '37signals').first and it will return a Firm object.
So, try this code
class Car < ActiveRecord::Base
set_table_name 'Vehicle_Table'
self.inheritance_column = :vehicle_type
end
Commented above but had limited editing abilities. I came across this exact problem and found the second half of the solution elsewhere. STI will allow you to get a subset of a table based on a column in the table but it will key off of the class name to find the records for that class. For example:
class Company < ActiveRecord::Base; end
class Client < Company; end
This will look at the table named Company for records that have the value 'Client' in a column named 'Type'.
You can override the column that STI checks by doing
class Company < ActiveRecord::Base
self.inheritance_column = :company_type
end
But it still looks for that column to contain 'Client'. You can override what value it looks for by doing this:
class Client < Company
def self.sti_name
1
end
end
This will now look at the company_type column for rows with a value of 1.
For Rails-4.2 this is nearly identical but does not need a class method:
private
self.inheritance_column = :company_type
def sti_name
1
end