I have three models.
Mailing
has_many :trackers, :as => :trackable
has_many :links
Link
belongs_to :mailing
has_many :trackers, :as => :trackable
Tracker
belongs_to :trackable, :polymorphic => true
The trackers table has the following columns
trackable_id trackable_type
Say I have a mailing object of id 1.
I want to get all of the trackable objects of type "Link" and "Mailing"
mailing = Mailing.find(1)
mailing.trackers gives me all of the objects that match trackable_type = "Mailing" trackable_id = 1
I want to get all of the records for this mailing from the trackers of both trackable types.
When I do
mailing.trackers.where(:tracker_id => mailing.links, :trackable_type => "Link")
I get nothing.
How can I get this data?
Thanks
I was able to do this as follows:
Mailing.rb
Tracker.where("trackable_id = ? and trackable_type = ? or trackable_id in (?) and trackable_type = ?",self.id, "Mailing", self.links, "Link")
This returns all there records of both type Mailing and Link for the given ids.
Related
I want to do a simple join. I have two tables: "candidates" and "notes".
Not all candidates have notes written about them, some candidates have more than one note written about them. The linking fields are id in the candidates table and candidate_id in the notes table. The query is:
people = candidates.where(:industry => industry).where("country = ?", country).left_outer_join(:notes, :candidate_id => :id).order(Sequel.desc(:id)).map do |row|
{
:id => row[:id],
:first => row[:first],
:last => row[:last],
:designation => row[:designation],
:company => row[:company],
:email => row[:email],
:remarks => row[:remarks],
:note => row[:note]
}
end
It works kind of fine and gets all the specified candidates from the candidates table and the notes from the notes table but where there is more than one note it repeats the name of the candidate. In the resulting list, person "abc" appears twice or three times depending on the number of notes associated with that person.
I am not actually printing the notes in the HTML result just a "tick" if that person has notes and "--" if no notes.
I want the person's name to appear only once. I have tried adding distinct in every conceivable place in the query but it made no difference.
Any ideas?
In order for distinct to work, you need to make sure you are only selecting columns that you want to be distinct on. You could try adding this to the query
.select(:candidates__id, :first, :last, :designation, :company, :email, :remarks, Sequel.as({:notes=>nil}).as(:notes)).distinct
But you may be better off using a subselect instead of a join to check for the existence of notes (assuming you are using a decent database):
candidates.where(:industry => industry, :country=>country).select_append(Sequel.as({:id=>DB[:notes].select(:candidate_id)}, :note)).order(Sequel.desc(:id)).map do |row|
{ :id => row[:id], :first => row[:first], :last => row[:last], :designation => row[:designation], :company => row[:company], :email => row[:email], :remarks => row[:remarks], :note => row[:note] }
end
I have a model Defect with attribute found_in. I have a hash test_phases whose keys are various test phases and whose value is an array of found_in values. Is there a way to group the Defect by test_phases? something like Defect.group_by(?test_phases)?.
The code I'm using is ugly
defects = {}
test_phases.each do |test_phase, found_ins|
defects[test_phase] ||= Defect.where(found_in: found_ins].all
end
You don't need to group, since you are iterating a hash (no duplicate keys), the output hash won't have multiple elements by key. Simply use map + Hash (or mash for Facets connoisseurs):
defects = Hash[test_phases.map do |test_phase, found_in_values|
[test_phase, Defect.where(found_in: found_in_values)]
end]
I solved this by creating a TestPhase model with a join table DefectFound
test_phase.rb:
has_many :defect_founds
defect_found.rb:
belongs_to :test_phase
defect.rb:
belongs_to :defect_found, foreign_key: :found_in, primary_key: :name # defect_found.name = defect.found_in
has_one :test_phase, through: :defect_found
controller:
#defects = Defect.includes(:test_phase).select('found_in, COUNT(id) as backlog').group(:found_in).group_by(&:test_phase)
view:
%table
- #defects.each do |test_phase, backlogs|
%tr
%td= test_phase.name if test_phase.present?
%td= backlogs.map(&:backlog).inject(:+)
I am sorry if I am asking the question poorly. I have a Rails 3.1 app with models (simplified) like so:
class Employee < ActiveRecord::Base
has_many :merged_children, :class_name => 'Employee', :foreign_key => "merge_parent_id"
has_many :timesheets
def total_time
merged_children.timesheets.in_range(range).hours_minutes.sum
end
end
class Timesheet < ActiveRecord::Base
belongs_to :employee
def in_range(range)
# filter records based on transaction_date in range
end
def hours_minutes
(hours + minutes/60.0).to_f
end
end
Note: The in_range method acts as a scope, essentially, and hours_minutes is a calculation. hours_minutes is valid for each timesheet record in the resulting dataset, and then total_time should sum those values and return the amount.
The "total_time" method is not working because employee.merged_children returns an array and timesheets is meant to run against a single Employee object.
Is there any way to structure the "total_time" so that it still sends one query to the db? It seems inelegant to iterate over the merged_children array, issuing a query for each. Not sure if a direct call to an Arel table would help or hurt, but I am open to ideas.
If we get it right, the resulting SQL should effectively look something like:
SELECT sum(hours + minutes/60.0)
FROM employees e1 join employees e2 on e1.id = e2.merge_parent_id join timesheets t on t.employee_id = e2.id
WHERE e1.id = [#employee.id] and t.transaction_date BETWEEN [#range.begin] and [#range.end]
Thanks so much!
The easiest thing here might be to add
has_many :children_timesheets, :through => :merged_children, :source => :timesheets
To your employee model,
Then (assuming in_range is actually a scope, or a class method that does a find)
children_timesheets.in_range(...)
Should be the collection of timesheets you're interested in and you can do something like
children_timesheets.in_range(...).collect(&:hours_minutes).sum
Untested with actual data.
range = ((1.day.ago)...(2.days.ago))
merge_parent = Employee.find(some_id)
Timesheet.where(:transaction_date => range)
.joins(:employee).where(:employees => {:merge_parent_id => merge_parent.id})
.sum('hours*60 + minutes')
(0.3ms) SELECT SUM(hours*60 + minutes) AS sum_id FROM "timesheets" INNER JOIN "employees" ON "employees"."id" = "timesheets"."employee_id" WHERE "employees"."merge_parent_id" = 1 AND ("timesheets"."created_at" >= '2011-12-13 03:04:35.085416' AND "timesheets"."created_at" < '2011-12-12 03:04:
Returns "0" for me. So hopefully it will return something nicer for you
I think this is a simple problem, but i just cant get my head round the solution. If i have a collection of records (reports in this case):
#reports = Report.all, :conditions => ["score > 10"]
and then i try to find the associated collection of other type of records (users in this case) i, naively, try this - but know from the offset that it wont work:
#users = User.find :all, :conditions => ["id IN (?)", #results.user_id]
So, how do i efficiently extract the #users collection of records?
Assuming that User has_many :reports
#users = User.joins(:reports) # all users that have reports
If you only want all users for some specific reports
#users = User.joins(:reports).where("reports.id IN (?)", #reports.map(&:id))
In Ruby on rails, our model includes orders and payments.
There's 1-many relationship between order and payments.
In the orders model we specify:
has_many :payments, :as => :payable
And a payment record has payable_id that is set to order.id.
In a report, I want to select all payments that belong to orders of a given type.
Using:
payments = Payment.find(:all, :conditions => conditions)
and adding 'payable.type="a" ' to the conditions doesn't work.
It seems that ActiveRecord doesn't develop this into a correct join statement (payable_id=order.id and orders.type='a').
I cannot use explicit SQL here, as the condition contains other things that are inserted there earlier in the code.
Thanks,
Raffi Lipkin
Your conditions clause is wrong.
You state that an Order
has_many :payments, :as => :payable
This tells me that a Payment
belongs_to :payable, :polymorphic => true
This means that the payments table has two columns of note: payable_id and payable_type. This also means that Payments can be applied not just to Orders, but also to other models as well (CreditCardBalances, who knows).
If you want to query for payments of a specific type, i.e. belonging to any instance of a particular class, you need to be querying the field payments.payable_type. This works fine:
Payment.find(:all, :conditions => "payable_type = 'Order'")
Here's a gist that shows what I did to test this. The models created are set up just like described above.
Don't forget that you can extract that into named scopes if it's easier:
named_scope :on_orders, :conditions => "payable_type = 'Order'"
Which makes it
Payment.on_orders
Or dynamically:
named_scope :on, lambda { |type| { :conditions => "payable_type = '#{type.to_s}'" } }
Which then makes it
Payment.on(Order) # or Payment.on(CreditCardBalance) or Payment.on("Order")
Try incliding and reference the actual table id name in the condition, rather than the association alias:
find(:include => "payments", :conditions => ["payment.type = ?", "x"]
You mention 'payment type'. If they're fairly static, have you considered using single table inheritance (STI) to subclass your different payment types? Then Rails will do all the magic to filter on type.
E.g.
class CreditCardPayment < Payment
...
end
It doesn't even need to exhibit different behaviour initially; however you'll probably find that it turns out to be really useful to have different data and polymorphic behaviour around payments.