HATBM association, how can I get pairs of associated models? - ruby-on-rails

Given following models and association:
(source: rubyonrails.org)
How can I get an array of pairs (physician_name, patient_name) that are appointed for certain day (appointment_date)? You can assume that one patient will never go to the same physician twice. Never.
I already tried things like:
#appointments = Appointment.where(appointment_date: params[:date])
but I have no idea what to do further. Should I iterate through this array and get every pair like this below?
#appointments.each do |appointment|
#physician = Physicians.where(id: :appointment.physician_id)
#patient = Patients.where(id: :appointment_patient_id)
I believe there's much easier way.
I'm using Rails 4.2.5.1.

I think what you want is approximately this:
Appointment.includes([:physician, :patient]).where(:date => appointment_date).map{|a| [a.physician.name, a.patient.name]}
Since only the Physician and Patient models have the names, they'll need to be loaded in the query (ok, you could avoid it by doing some fancy SQL trickery, but this is database-agnostic, which is convenient). Hence includes, which eager-loads associated models.
Then use .where to return only the appointments on the day you want (may be more complex if you're actually setting times in those DateTime values).
And finally, iterate over the list and return an Array of Arrays (Ruby not having Tuples) containing the names.

Related

Rails: Order custom model using custom method

I have a custom model called product, and it has many reviews.
i have a method that calculates the review
def rating
total = 0
reviews_count = reviews.count
return 0 if reviews_count == 0
reviews.each do |review|
total += review.grade
end
total.to_f/reviews_count
end
i would like to know how could i use this method to Order my products.
At products_controller.rb, if i use:
#products = Product.all.order("price")
its easy, it gives me the products list ordered by price. But, if i use, for example:
#products = Product.all.sort_by{|p| p.rating}
it gives me an array and not a "ActiveRecord::Relation"
I would like to know how could i order my product using a custom method that returns a value.
In general, you can't. Ordering happens in your database, which has no knowledge about any method you typed in your application. What you need is a way of translating your method into a valid sql. In your case, you can do:
Product.joins(:reviews).group('products.id').order('AVG(reviews.grade)')
That will give you sorted results and the relation object. However, relations with join are not that nice to work with, especially if you try to add another join. Also this might get quite slow when your database grows.
What you're doing in your example is running the query then using sort_by to sort the result set.
If you want to get back an activerecord collection instead of an array, and potentially chain this with other scopes, you should move the logic from your method into SQL, and put it in a scope.

How to remove some items from a relation?

I am loading data from two models, and once the data are loaded in the variables, then I need to remove those items from the first relation, that are not in the second one.
A sample:
users = User.all
articles = Articles.order('created_at DESC').limit(100)
I have these two variables filled with relational data. Now I would need to remove from articles all items, where user_id value is not included in the users object. So in the articles would stay only items with user_id, that is in the variable users.
I tried it with a loop, but it was very slow. How do I do it effectively?
EDIT:
I know there's a way to avoid doing this by building a better query, but in my case, I cannot do that (although I agree that in the example above it's possible to do that). That thing is that I have in 2 variables loaded data from database and I would need to process them with Ruby. Is there a command for doing that?
Thank you
Assuming you have a belongs_to relation on the Article model:
articles.where.not(users: users)
This would give you at most 100, but probably less. If you want to return 100 with the condition (I haven't tested, but the idea is the same, put the conditions for users in the where statement):
Articles.includes(:users).where.not(users: true).order('created_at DESC').limit(100)
The best way to do this would probably be with a SQL join. Would this work?
Articles.joins(:user).order('created_at DESC').limit(100)

Extraction of ids from a collection of objects

I have a collection of objects students. I want to get only ids of all the students. Student model is implemented in Datamapper. I am new to Ruby on Rails and Datamapper. Is there any way so that I can get id of all the students in collection students. So basically I want the following thing:
students = Student.all
ids = students.get_ids
I don't know how to implement get_ids.
Variable "students" is an array, you should not apply get_ids on it.
The function should get no parameters.
def get_ids
Student.all.map { |student| student.id }
end
If you really need all the student objects, use students.map(&:id) which is short for students.map{|s| s.id} and returns an array of all ids.
To get the ids directly from your database, use Student.where(...).pluck(:id) without all, which is much faster and less memory intensive than instantiating all the student objects.
Edit:
Sorry, the pluck method is ActiveRecord only. But the other answers have an alternative with the fields option.
If you just want to get an active record relation, try this:
Student.select(:id)
That should return you an ActiveRecord relation with all the objects and IDs in each object.
If what you want is not an ActiveRecord relation, but rather just the ids, go with the simple "pluck" method
Student.pluck(:id)
You need fields
Student.all(:fields=>[:id])
See more : How to fetch only specified fields of model with DataMapper?

Left outer join using the model of the joined table in rails

I need to do a left outer join in rails, but I need the model objects to be for the joined table.
What I want is a list of the days, with the metrics for each day. I need to have all days regardless of whether or not there were metrics, but I don't want to make a bunch of round trips to the database.
This works, but causes problems because it thinks I have PeriodDay objects when I really want Metric objects:
PeriodDay.select("metrics.*").join('LEFT OUTER JOIN metrics ON period_days.date = metrics.date').where('period_id = ?', current_period)
I can use find_by_sql on the Metric object, but the query building is more complicated (and conditional) than this simplified example, so I would rather figure out the "rails way" for this problem.
My current workaround is to loop through the records and create Metric objects from the attributes of the PeriodDay object. It doesn't feel efficient, but it is better than making multiple database calls.
metrics = []
recs = PeriodDay.select("metrics.*").join('LEFT OUTER JOIN metrics ON period_days.date = metrics.date').where('period_id = ?', current_period)
for rec in recs
metrics << Metric.new(rec.attributes)
end
Assuming that Period has many PeriodDay has many Metric, and that period_id is an attribute of your PeriodDay model, your workaround should be identical to something like this:
Metric.includes(:period_day).where(:period_day => {:period_id => #current_period})
This doesn't get you a list of days with their respective Metric objects as you mentioned in the original question, but it gets you a list of all Metric objects for a particular period. (unless I'm missing something...)
If you want a list of PeriodDay objects with their included Metric objects, you can use includes instead of joins.
PeriodDay.includes(:metrics).where(:period_id => #current_period)
This will execute two queries (one to get period days and the other to get metrics) but it is a lot more readable.

Best practices for getting a list of IDs from an ActiveRecord model

I have an ActiveRecord model Language, with columns id and short_code (there are other columns, but they are not relevant to this question). I want to create a method that will be given a list of short codes, and return a list of IDs. I do not care about associations, I just need to end up with an array that looks like [1, 2, 3, ...].
My first thought was to do something like
def get_ids_from_short_codes(*short_codes)
Language.find_all_by_short_code(short_codes.flatten, :select => 'id').map(&:id)
end
but I'm not sure if that's needlessly wasting time/memory/processing.
My question is twofold:
Is there a way to run an ActiveRecord find that will just return an array of a certain table column rather than instantiating objects?
If so, would it actually be worthwhile to collect an array of length n rather than instantiating n ActiveRecord objects?
Note that for my specific purpose, n would be approximately 200.
In Rails 3.x, you can use the pluck method which returns the values from the requested field without instantiating objects to hold them.
This would give you an array of IDs:
Language.where(short_code: short_codes.flatten).pluck(:id)
I should mention that in Rails 3.x you can pluck only one column at a time but in Rails 4 you can pass multiple columns to pluck.
By the way, here's a similar answer to a similar question
Honestly, for 200 records, I wouldn't worry about it. When you get to 2000, 20,000, or 200,000 records - then you can worry about optimization.
Make sure you have short_code indexed in your table.
If you are still concerned about performance, take a look at the development.log and see what the database numbers are for that particular call. You can adjust the query and see how it affects performance in the log. This should give you a rough estimate of performance.
Agree with the previous answer, but if you absolutely must, you can try this
sql = Language.send(:construct_finder_sql, :select => 'id', :conditions => ["short_code in (?)", short_codes])
Language.connection.select_values(sql)
A bit ugly as it is, but it doesn't create in-memory objects.
if you're using associations you can get raw ids directly from ActiveRecord.
eg.:
class User < ActiveRecord::Base
has_many :users
end
irb:=> User.find(:first).user_ids
irb:>> [1,2,3,4,5]
Phil is right about this, but if you do find that this is an issue. You can send a raw SQL query to the database and work at a level below ActiveRecord. This can be useful for situations like this.
ActiveRecord::Base.connection.execute("SQL CODE!")
Benchmark your code first before you resort to this.
This really is a matter of choice.
Overkill or not, ActiveRecord is supposed to give you objects since it's an ORM. And Like Ben said, if you do not want objects, use raw SQL.

Resources