SQL UNION with Rails ActiveRecord - sql

I have two models in my app, notes and highlights. They are defined like:
class Note < ActiveRecord::Base
belongs_to :user
end
class Highlight < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, I want both accessible in the same stream, sorted by creation date. I couldn't wrap my head around how to do this in ActiveRecord, so I put together this query:
SELECT book_id, page, content, created_at FROM `highlights`
INNER JOIN `highlights_users` ON `highlights`.id = `highlights_users`.highlight_id
WHERE (`highlights_users`.user_id = 1 )
UNION SELECT book_id, page, content, created_at FROM notes
ORDER BY created_at DESC
Add of course, it works, but doesn't feel very Rails-y. Plus, I don't know how to tell which items are notes and which are highlights. The other option is to get the note stream and highlight streams separately, then merge the arrays. That also seems clumsy, and I feel like there must an abstraction somewhere for what I'm trying to do.
Is there some key ActiveRecord functionality I'm missing here? What's the most efficient way to go about this?

Single table inheritance is likely the abstraction that you are looking for.
http://ar.rubyonrails.org/classes/ActiveRecord/Base.html
http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html

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.

Get all children of has_many through self-join in single query

I am having a library full of Articles. An article references other articles through citations. So it's self-referencing through citations, like this:
class Article < ActiveRecord::Base
has_many :citations
has_many :referenced_articles, through: :citations
end
class Citation < ActiveRecord::Base
belongs_to :Article
belongs_to :referenced_article, :class_name => "Article"
end
The question:
How can I, given some article, fetch all citated articles: both directly citated and indirectly citated by it's referenced articles, etc. until reaching the articles that do not have any citations (in my case that will probably be no more than 15 levels deep).
My current method:
Given some article "main_article", I can get it's referenced_articles by
sub_articles = main_article.referenced_articles
And to go one level deeper I do:
sub_articles = Article.eager_load(:referenced_articles).where(id: sub_articles)
sub_sub_articles = sub_articles.collect {|x| x.referenced_articles}.flatten
Putting this in a loop gives the desired result, going one level deeper every iteration.
But i am hoping for a nicer solution, as I am currently fetching every article twice.
Boundary condition: I don't want to add a referencing instance to every article in my library telling it where it is used(citated). So I guess the 'ancestry' gem is not an option :(
I don't have any experience with recursive queries, but if your database engine supports them you might want to look at common-table-expressions with recursion:
http://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression
Oracle, sqlserver, and postgres all seem to support recursive cte's.
You'll probably have to craft the raw sql for this yourself, I doubt you'll find much support in rails for doing this with a single query.

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'

ActiveRecord Query: select record where has at least one in a has many relationship

Say you have this:
class Question < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
end
How do I search for all the questions that have answers without getting duplicate questions?
Say a question has two answers, if you do this:
Question.joins(:answers)
because it is an inner join, you'll get the question multiple times.
Is there a way to do this through the query interface without having to do a raw sql distinct or unique?
I would just use a counter cache column. This gives you a database column in questions which counts the number of answers attached to it. As a bonus, it's probably faster to run this query.
Here's a Railscasts video that describes how to create one: http://railscasts.com/episodes/23-counter-cache-column
Then your query becomes:
Question.where("answers_count > 0")
You could even be fancy and define this as a scope:
class Question < ActiveRecord::Base
has_many :answers
scope :answered, where("answers_count > 0")
end
It seems like a simple solution, but simple is good.
This should work:
Question.joins(:answers).uniq