rails sql find models related to self with attribute difference greater than threshold - sql

I have a Person model in rails that is related to itself:
class Person < ApplicationRecord
has_many :children,
class_name: "Person",
foreign_key: :person_id
belongs_to :mother,
class_name: "Person",
foreign_key: :person_id
end
People also have an age attribute representing their ages in years. I would like an ActiveRecord or SQL query to find all people with child-to-mother age discrepancies that are greater than or equal to 50 years.
(The model name here is not what I'm truly using in my app. I renamed it to simplify the problem description. However, for my actual query I will need to know if the absolute value of the age difference is greater than or equal to 50 years. Obviously children can't be older than their mothers so the analogy doesn't hold there.)
I have tried the following and several variants, which are all crashing:
Person
.includes(:mother)
.where("ABS(persons.age - (SELECT age FROM persons WHERE (id = persons.person_id))) >= 50")
This one in particular results with a hint:
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
At first I thought that maybe the latest SQL querie's where clause was returning some sort of list of ages rather than just the mother's age, so I tried subscripting with a [1] after it to get the first element of (what should be) a single element array (if my hypothesis was correct). It was not correct - the SQL error then updated to let me know I couldn't subscript something that wasn't an array.
I'm looking for the simplest solution. Any help is appreciated. Thanks!

Not in a position to test this but I believe reails will generate aliases for you on the fly.
Try
Person
.includes(:mother)
.where("ABS(persons.age - (SELECT age FROM mothers_persons)) >= 50")
Generally it's association name pluralized, underscore, table name (so if users have employees the associated table (alias) of user's employees is called employees_users
If the above doesn't work, you would need to create aliases manually in your SQL call.

Related

Finding the average number of a has many association between two models using Rails ActiveRecord query interface

Lets say I have two models: :User and :Ringtone. A :User can have many :Ringtones and a :Ringtone belongs to a user. I would like to be able to use the ActiveRecord Query interface to calculate the average number of :Ringtones that belongs to a :User, but am struggling to figure out how.
Using raw sql I can get the data like:
ActiveRecord::Base.connection.execute("
(with tones_count as
(select
user_id, count(ringtones.id)
from
ringtones
group by 1)
select avg(count) from tones_count)").values
But this is not ideal, and I would much rather be able to use ActiveRecord for this query/calculation...
I would much rather be able to use ActiveRecord, you could do like so
table = Arel::Table.new(:ringtones)
Ringtone.from(
Ringtone.select(
table[:user_id],
table[:id].count.as("tones_count")
).group(1)
).average("tones_count")

Rails/ActiveRecord: Can I perform this query without passing the SQL string to #order?

I have two models Issue and Label. They have a many to many relationship.
I have a method that returns the ten labels that point to the most issues.
class Label < ApplicationRecord
has_many :tags
has_many :issues, through: :tags
def self.top
Label.joins(:issues)
.group(:name)
.order('count_id desc')
.count(:id)
.take(10)
end
end
It does exactly what I expect it to but I want to know if it's possible to compose the query without the SQL string.
order('count_id DESC') is confusing me. Where does count_id come from? There isn’t a column named count_id.
Label.joins(:issues).group(:name).column_names
#=> ["id", "name", "created_at", "updated_at"]
I’ve found some SQL examples here. I think it’s basically the same as ORDER BY COUNT(Id):
SELECT COUNT(Id), Country
FROM Customer
GROUP BY Country
ORDER BY COUNT(Id) DESC
Is it possible to perform the same query without passing in the SQL string? Can it be done with the ActiveRecord querying interface alone?
If you look at your query log, you'll see something like:
select count(labels.id) as count_id ...
The combination of your group call (with any argument) and the count(:id) call gets ActiveRecord to add the count_id column alias to the query. I don't think this is documented or specified anywhere (at least that I can find) but you can see it happen if you're brave enough to walk through the Active Record source.
In general, if you add a GROUP BY and then count(:x), Active Record will add a count_x alias. There's no column for this so you can't say order(:count_id), order(count_id: :desc), or any of the other common non-String alternatives. AFAIK, you have to use a string but you can wrap it in an Arel.sql to prevent future deprecation issues:
Label.joins(:issues)
.group(:name)
.order(Arel.sql('count_id desc'))
.count(:id)
.take(10)
There's no guarantee about this so if you use it, you should include something in your test suite to catch any problems if the behavior changes in the future.

Allow users to create dynamic model attributes?

In my Rails3 app, I am using ActiveRecord and Postgresql.
Say I have a model called Parts. The model has a small list of standard attributes such as price, quantity, etc.
However, Customer A might want to add LotNumber and CustomerB might want to add OriginalLocation.
How would I allow them to do that?
I thought about creating a PartsDetail model that allowed them to have a type.
class PartsDetail < ActiveRecord::Base
attr_accessible :type, :value, :part_id
belongs_to :parts
end
So that "type" could be "LotNumber", etc.
But I'm not quite sure how that would work in my associations and querying.
Any ideas?
Thanks.
Since you're using PostgreSQL, you could use hstore to store arbitrary hashes in database columns:
This module implements the hstore data type for storing sets of key/value pairs within a single PostgreSQL value. This can be useful in various scenarios, such as rows with many attributes that are rarely examined, or semi-structured data. Keys and values are simply text strings.
There's even a gem for adding hstore support to ActiveRecord:
https://github.com/softa/activerecord-postgres-hstore
Then you could create an hstore column called, say, client_specific and look inside it with things like:
M.where("client_specific -> 'likes' = 'pancakes'")
M.where("client_specific #> 'likes=>pancakes'")
to see which clients have noted that they like pancakes.
You might want to store a list of customer-specific fields somewhere with the customer record to make the UI side of things easier to deal with but that's pretty simple to do.
Here's a gist that someone wrote that allows you to use hstore attributes in your models more seamlessly: https://gist.github.com/2834785
To use add this in an initializer ( or create a new initializer called active_record_extensions.rb )
require "hstore_accessor"
Then in your model you can have:
Class User < ActiveRecord::Base
hstore_accessor :properties, :first_name, :last_name
end
That allows you to do:
u = User.new
u.first_name = 'frank'
You can still do add attributes to the hstore column and bypass the hstore_attributes though:
u.properties['middle_name'] = 'danger'

Using a scope to access a unique single record without having to loop?

I have a model Enrollment which validates the uniqueness for each course by user:
validates_uniqueness_of :user_id, :scope => :course_id
I created a scope in the Enrollment model where I can pass in the course to narrow down the course by user:
scope :course, lambda { |course| where(:course_id => course.id) }
By doing this, I can call:
current_user.enrollments.course(#course)
However, it then makes me loop through the result, even though there is only one result (unique course for each user). This is fine, but it seems like there should be a way to simply do the scope and then just access the record without need to loop through one result.
Any ideas? I feel like I'm missing something.
Try this:
current_user.enrollments.course(#course).first
That will generate SQL query with LIMIT 1 statement and return the model directly without wrapping it into an array.
Small note about your scope. As your Enrollment model has course association (has_one :course I assume) you'd better give your scope different name like by_course to prevent collision with course association that allows you to fetch course for a given enrollment.

Rails Query Issue

I have photos which have_many comments.
I want to select whatever photos have recent comments and display those photos in a kind of "timeline" where the most recently commented photo is at the top and other photos fall below.
I tried this, and it worked on SQLite:
#photos = Photo.select('DISTINCT photos.*').joins(:comments).order('comments.created_at DESC')
However testing on PostgreSQL raises this error:
PGError: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
\n: SELECT DISTINCT photos.* FROM \"photos\" INNER JOIN \"comments\" ON \...
So, the problem is, I'm selecting Photos but ordering by recency of comments... and Postgre doesn't like that.
Can anyone suggest either:
A: How I can fix this query...
or
B: A different way to retrieve photos by the recency of their comments?
The important reason I'm doing it this way instead of through the comments model is I want to show each photo once with any recent comments beside it, not show each comment by itself with the same photos appearing multiple times.
Thanks!
Check out the :touch parameter of of the belongs_to association:
:touch
If true, the associated object will be
touched (the updated_at/on attributes
set to now) when this record is either
saved or destroyed. If you specify a
symbol, that attribute will be updated
with the current time instead of the
updated_at/on attribute.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to
In your Comment model, therefore, you would have:
belongs_to :photo, :touch => :comments_updated_at
Now, in order to create a time line of photos with recently updated comments all you need to do is:
Photo.order('comments_updated_at DESC').all
Just be sure to add the "comments_updated_at" datetime field to your Photo model.
Make sense?
Just for the future readers of this question, the real answer to your SQL issue in SQlite vs Postgresql is that in the SQL "standard", every selected column needs to be in the GROUP BY or be an aggregate function.
https://www.techonthenet.com/sql/group_by.php (or whatever SQL ref you want to take a look at)
Your SQLite query used SELECT * instead of specific columns. That would have blown up with a similar error on most databases like Postgresql (MySQL, Maria, probably MSSQL Server). It's definitely invalid SQL grammar for a lot of good reasons.
Under the hood, I have no clue what SQlite does -- maybe it expands the * into fields and adds them to the GROUP BY under the hood? But its not a good SQL statement which is which it threw the error.