i have a country model and a travel note model. A country has many travel notes and a travel note belongs to one country.
class TravelNote < ActiveRecord::Base
default_scope { order(created_at: :desc) }
belongs_to :country
has_many :chapters
before_destroy { draft? }
validate :validates_uniqueness_of_draft, on: :create
enum status: { draft: 0, published: 1, archived: 2 }
enum advice_against: { no: 0, general: 1, tourists: 2 }
scope :country, ->(country_id) { where(country_id: country_id) }
# further methods omitted...
end
class Country < ActiveRecord::Base
default_scope { order(:iso_code) }
has_many :travel_notes
end
in app/controllers/countries_controller.rb:
class CountriesController < ApplicationController
def index
#countries = Country.includes(:travel_notes)
end
# rest of the class omitted...
end
in app/views/countries/index.html.haml:
#countries.each do |country|
%tr
%td= link_to country.name_de, country_travel_notes_path(country)
%td= TravelNote.published.country(country.id).first.try :published_at
because of performance reason i want to remove TravelNote.published.country(country.id).first.try :published_at so that there is not hundreds of database queries anymore instead just an array of an equivalent sql query:
select * from countries
left join travel_notes
on countries.id=travel_notes.country_id
how can i achieve it?
Apparently you are trying to eager-load the "travel_notes" associated to the country:
Country.includes(:travel_notes).where(travel_notes: { status: 1} )
so your code will be:
class CountriesController < ApplicationController
def index
#countries = Country.includes(:travel_notes).where(travel_notes: { status: 1} )
end
# rest of the class omitted...
end
#countries.each do |country|
%tr
%td= link_to country.name_de, country_travel_notes_path(country)
%td= country.travel_notes.first.published_at
You could write a custom scope to include only the published notes.
something like
scope :include_published, -> { proc do |_|
joins("LEFT OUTER JOIN (
SELECT b.*
FROM travel_notes b
WHERE published = 1
GROUP BY b.country_id
) notes_select ON notes_select.country_id = countries.id"
).select("countries.*").select("#{insert what attributes you want to include }")
end.call(self, counted_model) }
You have to include the attributes you want from the note in the second select clause then they will be included in the country active record result as methods with the same name.
The SQL-query can be written prettier, I just threw something together...
I use a similar technique in my project but in order to include counts of associated objects.
Related
I have models Category and Transactions.
Category has_many transactions, Transaction belongs_to category.
And i have scope for Category:
#relation = Category.all
#relation.joins(:transactions).where('transactions.created_at >= ?', 1.month.ago).
group('categories.id').order('SUM(transactions.debit_amount_cents) DESC')
It displays categories and sorts them by sum of transactions.debit_amount_cents
I want to display the amount for all its transactions along with each category.
Like:
id: 1,
name: "Category1",
all_amount: *some value* #like this
How can I improve this scope?
class Category < ApplicationRecord
# remember that scope is just a widely abused syntactic sugar
# for writing class methods
def self.with_recent_transactions
joins(:transactions)
.where('transactions.created_at >= ?', 1.month.ago)
.select(
'categories.*',
'SUM(transactions.debit_amount_cents) AS total_amount'
)
.order('total_amount DESC')
.group('categories.id')
end
end
If you select a column or an aggregate and give it an alias it will be available on the resulting model instances.
Category.with_recent_transactions.each do |category|
puts "#{category.name}: #{category.total_amount}"
end
For portability you can write this with Arel instead of SQL strings which avoids hardcoding stuff like table names:
class Category < ApplicationRecord
def self.with_recent_transactions
t = Transaction.arel_table
joins(:transactions)
.where(transactions: { created_at: Float::Infinity..1.month.ago })
.select(
arel_table[Arel.star]
t[:debit_amount_cents].sum.as('total_amount')
)
.order(total_amount: :desc) # use .order(t[:debit_amount_cents].sum) on Oracle
.group(:id) # categories.id on most adapters except TinyTDS
end
end
In Rails 6.1 (backported to 6.0x) you can use beginless ranges to create GTE conditions without Float::Infinity:
.where(transactions: { created_at: ..1.month.ago })
I have a site where customers can purchase tickets for an event and created a Reporter::EventTickets class to generate a hash for the relevant information I need. Reporter::EventTickets is called in a Reports controller, and ultimately I want to view this on an admin report page.
I cannot get the information to display on my table in my haml view file!
I've tried everything I can think and am at a complete loss for what to do.
Information is being saved to the database (PostgreSQL) successfully and the EventTickets class is transforming data properly (passing RSpec tests).
Code:
class Reporter::EventTickets
include Virtus.model
def events
products.map do |product|
line_items = LineItem.includes(:order)
.where("orders.completed_at IS NOT NULL")
.where(purchasable_type: "Product", purchasable_id: product.id)
.references(:order)
ticket_purchases = line_items.map do |line_item|
order = line_item.order
[order.bill_address_full_name, order.email, line_item.quantity]
end
total = ticket_purchases.reduce(0) { |sum, purchase| sum + purchase.last }
{
date: product.event_date,
name: product.name,
total: total,
purchases: ticket_purchases.sort
}
end
end
private
def products
Ticket.where("event_date >= ?", week_ago_in_time_zone).order("event_date ASC")
end
def week_ago_in_time_zone
(Time.current - 7.days).to_date
end
end
Controller:
def event_tickets
#reporter = Reporter::EventTickets.new
#csv_link = admin_reports_event_tickets_path(format: :csv)
respond_with #reporter
end
Models:
class Order < ActiveRecord::Base
has_many :line_items, dependent: :destroy
class LineItem < ActiveRecord::Base
belongs_to :order, touch: true
belongs_to :purchasable, polymorphic: true
class Product < ActiveRecord::Base
include Purchasable
has_many :line_items, as: :purchasable
View:
= render partial: "admin/shared/header", locals: {title: "Event Ticket Purchases"}
.container-fluid.padded
= render partial: "admin/shared/notifications"
.row-fluid.hidden-print
= link_to "Download CSV", #csv_link, class: "btn btn-blue"
.row-fluid
- #reporter.events.each do |event|
%h3
= event[:date].strftime("%_m/%e")
= event[:name]
.box
%table.table.table-normal
%thead
%tr
%td Name
%td Email
%td Tickets
%tbody
- event[:purchases].each do |purchase|
%tr
- purchase.each do |column|
%td= column
%tr
%td{:colspan => "2"}
%b TOTAL
%td
%b= event[:total]
There are no errors from Rails and the page loads with the title and button. The table just does not populate.
As debugged in comments, your code is fine. With live data, this:
Ticket.where("event_date >= ?", week_ago_in_time_zone).order("event_date ASC")
...however, is returning an empty result set. So this:
products.map do |product|
...
end
...is returning an empty array.
Your RSpec test passed because you had Ticket records in your test data that met the criteria and, therefore, products.map was returning a non-empty array.
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.
Given I have two models:
class Post < ActiveRecord::Base
belongs_to :district
end
class District < ActiveRecord::Base
has_many :posts
end
I need to make a check_boxes filter in ActiveAdmin on Posts page for with ability for user to get posts that belong to some exact districts or does not belong to any districts at all.
Before ActiveAdmin changed MetaSearch to Ransack, that could be done with custom scope. And now I don't have any idea. When I do the following:
filter :district_id_null, as: :boolean
filter :district, as: :check_boxes
It makes condition WHERE district_id IN (1,2,3) AND district_id IS NULL (I need OR instead of AND). And when I do
filter :district, as: :check_boxes, collection: proc { District.unscoped.map { |x| [x.title, x.id] }.unshift ['Empty', 'null'] }
It makes condition WHERE district_id IN (0,1,2,3) (but in most SQL databases NULL is not 0).
I think something like this might work
class Post
def self.ransack_with_or(search_params)
params = search_params.deep_clone
#check if we need OR
if search_params.has_key?('district_id_in') && search_params.has_key?('district_id_null')
params.delete('district_id_null')
district_id_in = params.delete('district_id_in')
#old behaviour without district_id_null and district_id_null attributes
q = ransack_without_or(params)
#here we're adding OR group
q.build_grouping({m: 'or', district_id_null: true, district_id_in: district_id_in})
else
#old behaviour we don't need OR
q = ransack_without_or(params)
end
q
end
#wrapping ransack method
class << self
alias_method_chain :ransack, :or
end
end
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 :)