Join tables on association - ruby-on-rails

I have three tables:
Product, Object, Info - Product has_many Objects through Product_Objects ...
Object has_many Info, Product has_many Info.
What do I need:
I want to access the values from a column in Info table.
How I do it now:
data = #product.objects
data.infos.where('date(created_at) IN (?)', dates).where(product_id: #product.id).each do |d|
d.value
end
(dates: Date.today, Date.today - 1.day, Date.today - 1.week, Date.today - 1.month)
Is there a way to this differently, because I have a lot of records, and is very slow.
Expected result: [name, value]
Name is a column in the Object table and value is a integer from the Info table, column value.
Thank you

You can speed it up by getting rid of date function and by using pluck instead of iterating over all records (assuming value is a column on Info model). To do this, create a scope on Info model:
scope :created_at, ->(dates) { where(created_at: Array.wrap(dates).map {|date| date..(date + 1.day)}) }
Then you can call:
data = #product.objects
data.infos.created_at(dates).where(product_id: #product.id).pluck(:value)
UPDATE:
Since those two columns are on separate tables, you will need to use joins method. This makes it slightly trickier to use the scope defined above - we need to use merge. ALso pluck do not allows to get more than one column at the time, so we need to use nice select_all trick:
data = #product.objects
query = data.joins(:infos).merge(Info.created_at(dates)).where(product_id: #product.id).select([:name, :value])
ActiveRecord::Base.connection.select_all(query)

Related

Ruby/rails sort_by not ordering properly?

I have a long array of Photo model objects, and I want to sort them by created_at, newest first, then get a new array with the first 21 photos.
My problem is that the final array is not ordered properly.
Here is my code:
#recent_photos = photos.sort_by(&:created_at).reverse.first(21)
when I print out #recent_photos the created_at values are ordered like this:
1458948707
1458943713
1458947042
1458945171
...
What is the correct way to sort objects?
UPDATE:
here's how the initial list is compiled:
photos = #user.photos
#following = #user.following
#following.each do |f|
photos += f.photos if f.id != #user.id
end
#user.memberships.each do |group|
photos += group.photos
end
SOLUTION:
problem was with the question - I wanted to sort by timestamp not created_at, and those were timestamp values in the output
You can crunch it all down into a single query:
#recent_photos = Photo.where(
user_id: #user.following_ids
).order('created_at DESC').limit(21)
You really do not want to be doing N queries for each of these as it will get slower and slower as a person has more people they're following. If they follow 10,000 people that's a ridiculous number of queries.
If you add a :through definition to your model you may even be able to query the photos directly:
has_many :follower_photos,
class_name: 'Photo',
through: :followers
Whatever your constraints are, boil them down to something you can query in one shot whenever possible. If that's not practical, get it down to a predictable number of queries, never N.
Try:
#recent_photos = Photo.order('created_at desc').first(21)

Rails sql query with dynamic attribute

Say I have an ActiveRecord object that contains a quantity and a price stored in the database.
I have defined a accessor for the total_price:
def total_price
quantity * price
end
Now what if I want to use this dynamic "attribute" in multiple ActiveRecord query contexts? I might to sum on it, compute average, for multiple scope, etc.
What would be the best practices so that I don't have to repeat this quantity * price with ActiveRecord and if I don't want to denormalize by writing it in DB?
Thanks!
Well we wanted to get caption (from join model) to appear on our associated image model (I.E if you called #user.images, you'd be able to call image.caption (even though caption was in the join model)
So we looked at this RailsCast (you'll benefit from around 6:40) which gave us some information about how you can use join to create more dynamic queries. We ended up using this:
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }
I'm thinking you could use something similar for your request (include some SQL to create the pseudo column in the object). Since it's the origin model, I'm thinking about a scope like this:
default_scope select("(table.quantity * table.price) as total_price")
I assume price is stored in the database. Is quantity stored in the database? If both are stored, why not make total_price a database column as well? You can update total_price whenever you update the record.'
class Order < AR::Base
before_update :update_total_price
def update_total_price
self[:total_price] = quantity * price
end
end
Obviously you can do anything you would with an ordinary column, like Order.where("total_price > 1.0") and what-not.

ActiveRecord query array intersection?

I'm trying to figure out the count of certain types of articles. I have a very inefficient query:
Article.where(status: 'Finished').select{|x| x.tags & Article::EXPERT_TAGS}.size
In my quest to be a better programmer, I'm wondering how to make this a faster query. tags is an array of strings in Article, and Article::EXPERT_TAGS is another array of strings. I want to find the intersection of the arrays, and get the resulting record count.
EDIT: Article::EXPERT_TAGS and article.tags are defined as Mongo arrays. These arrays hold strings, and I believe they are serialized strings. For example: Article.first.tags = ["Guest Writer", "News Article", "Press Release"]. Unfortunately this is not set up properly as a separate table of Tags.
2nd EDIT: I'm using MongoDB, so actually it is using a MongoWrapper like MongoMapper or mongoid, not ActiveRecord. This is an error on my part, sorry! Because of this error, it screws up the analysis of this question. Thanks PinnyM for pointing out the error!
Since you are using MongoDB, you could also consider a MongoDB-specific solution (aggregation framework) for the array intersection, so that you could get the database to do all the work before fetching the final result.
See this SO thread How to check if an array field is a part of another array in MongoDB?
Assuming that the entire tags list is stored in a single database field and that you want to keep it that way, I don't see much scope of improvement, since you need to get all the data into Ruby for processing.
However, there is one problem with your database query
Article.where(status: 'Finished')
# This translates into the following query
SELECT * FROM articles WHERE status = 'Finished'
Essentially, you are fetching all the columns whereas you only need the tags column for your process. So, you can use pluck like this:
Article.where(status: 'Finished').pluck(:tags)
# This translates into the following query
SELECT tags FROM articles WHERE status = 'Finished'
I answered a question regarding general intersection like queries in ActiveRecord here.
Extracted below:
The following is a general approach I use for constructing intersection like queries in ActiveRecord:
class Service < ActiveRecord::Base
belongs_to :person
def self.with_types(*types)
where(service_type: types)
end
end
class City < ActiveRecord::Base
has_and_belongs_to_many :services
has_many :people, inverse_of: :city
end
class Person < ActiveRecord::Base
belongs_to :city, inverse_of: :people
def self.with_cities(cities)
where(city_id: cities)
end
# intersection like query
def self.with_all_service_types(*types)
types.map { |t|
joins(:services).merge(Service.with_types t).select(:id)
}.reduce(scoped) { |scope, subquery|
scope.where(id: subquery)
}
end
end
Person.with_all_service_types(1, 2)
Person.with_all_service_types(1, 2).with_cities(City.where(name: 'Gold Coast'))
It will generate SQL of the form:
SELECT "people".*
FROM "people"
WHERE "people"."id" in (SELECT "people"."id" FROM ...)
AND "people"."id" in (SELECT ...)
AND ...
You can create as many subqueries as required with the above approach based on any conditions/joins etc so long as each subquery returns the id of a matching person in its result set.
Each subquery result set will be AND'ed together thus restricting the matching set to the intersection of all of the subqueries.

Fetch COUNT(column) as an integer in a query with group by in Rails 3

I have 2 models Category and Article related like this:
class Category < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :category
def self.count_articles_per_category
select('category_id, COUNT(*) AS total').group(:category_id)
end
end
I'm accessing count_articles_per_category like this
Article.count_articles_per_category
which will return articles that have 2 columns: category_id and total.
My problem is that total column is a string. So the question is: is there a method to fetch that column as an integer?
PS: I tried to do a cast in the database for COUNT(*) and that doesn't help.
I try to avoid doing something like this:
articles = Article.count_articles_per_category
articles.map do |article|
article.total = article.total.to_i
article
end
No, there is no support in ActiveRecord to automatically cast datatypes (which are always transferred as strings to the database).
The way ActiveRecord works when retrieving items is:
for each attribute in the ActiveRecord model, check the column type, and cast the data to that type.
for extra columns, it does not know what data type it should cast it to.
Extra columns includes columns from other tables, or expressions.
You can use a different query, like:
Article.group(:category_id).count
Article.count(:group => :category_id)
These return a hash of :category_id => count. So you might get something like {6=>2, 4=>2, 5=>1, 2=>1, 9=>1, 1=>1, 3=>1}.
Using the count method works because it implicitly lets ActiveRecord know that it is an integer type.
Article.group(:category_id).count might give you something you can use. This will return a hash where each key represents the category_id and each value represents the corresponding count as an integer.

Rails - find_by_sql - Querying with multiple values for one field

I'm having trouble joining the values for querying multiple values to one column. Here's what I got so far:
def self.showcars(cars)
to_query = []
if !cars.empty?
to_query.push cars
end
return self.find_by_sql(["SELECT * FROM cars WHERE car IN ( ? )"])
end
That makes the query into:
SELECT * FROM cars WHERE car IN (--- \n- \"honda\"\n- \"toyota\"\n')
It seems find_by_sql sql_injection protection adds the extra characters. How do I get this to work?
Do you really need find_by_sql? Since you're performing a SELECT *, and assuming your method resides on the Car model, a better way would be:
class Car < ActiveRecord::Base
def self.showcars(*cars)
where('car in :cars', :cars => cars)
# or
where(:car => cars)
end
end
Note the * right after the parameter name... Use it and you won't need to write code to make a single parameter into an array.
If you really need find_by_sql, try to write it this way:
def self.showcars(*cars)
find_by_sql(['SELECT * FROM cars where car in (?)', cars])
end
Try joining the to_query array into a comma separated string with all values in single quotes, and then passing this string as a parameter "?".
Problem resolve.
def self.average_time(time_init, time_end)
query = <<-SQL
SELECT COUNT(*) FROM crawler_twitters AS twitter WHERE CAST(twitter.publish AS TIME) BETWEEN '#{time_init}' AND '#{time_end}'
GROUP BY user) AS total_tweets_time;
SQL
self.find_by_sql(sanitize_sql(query))
end

Resources