I have a data model where users have timelogs (which have dates).
I'm trying to query my DB in a way that will let me spit out a table of data, listing each user that has timelogs, and the sum of the time logged for each day in the period queried (similar in essence to a weekly timesheet).
I'm having problems figuring out a way of doing this. Any ideas?
A lot of the details will depend on your data model, but the short answer is to create a relationship first:
class User < ActiveRecord::Base
has_many :user_timelogs
has_many :timelogs, :through => :user_timelogs
end
class UserTimelog < ActiveRecord::Base
belongs_to :user
belongs_to :timelog
end
class Timelog < ActiveRecord::Base
has_many :user_timelogs
has_many :users, :through => :user_timelogs
end
Once you do that, you can query timelogs for users:
User.all.timelogs
You can add on additional queries (specific dates, sums, etc.). Check out the Rails guides for more info on how to narrow down that query:
http://guides.rubyonrails.org/active_record_querying.html
Try this, assuming the times are stored as number of seconds in a 'duration' column for each timelog row:
Timelog.where(...). # your time period
joins(:users).
group('users.id').
sum('duration')
This will give you a hash with the user ids as keys, and the sum of the durations as values.
Related
How do I meld together getting all of the associated data for all of the records of a given model?
I have the following models:
User --N:1--> Reservation <--1:N-- Concert
So pseudo-code:
Reservation belongs_to User
Reservation belongs_to Concert
User has_many Reservations
User has_many Concerts through Reservations
Concert has_many Reservations
Concert has_many Users through Reservations
How do I make a single big array of everything?
I can get all my Reservations via Reservation.all
I can get the User for a particular Reservation via Reservation.find(25).user
I can get the Concert for a particular Reservation via Reservation.find(25).concert
But how do I get it for all of them? If I do
Reservation.all.each do |res|
res.user.name+","+res.concert.name+","+res.concert.date # etc.
end
Then it will do two new database queries for each reservation as it loops through. For 10 records, it might not matter, but for thousands, it can be very painful. Add to it other associations (e.g. Concert belongs_to venue, User has_one email, etc.)...
Is there any way to say, "Get all of the reservations and the following attached info" so it loads in a single SQL query?
What you're trying to accomplish is called eager loading, and can be done using includes in ActiveRecord. See below:
N + 1 queries problem
Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the includes method of the Model.find call. With includes, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
In your example you could use the following:
Reservation.all.includes(:user, :concert)
It is also a good idea to specify the :inverse_of option for your :belongs_to relations. This optimizes object loading and makes sure that cross-referencing a model will point back to the same object in memory, i.e.:
#user == #user.reservations.first.user # true
More information available here:
If you are using a belongs_to on the join model, it is a good idea to set the :inverse_of option on the belongs_to ...
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
In your example:
# app/models/reservation.rb
belongs_to :user, :inverse_of => :reservations
belongs_to :concert, :inverse_of => :reservations
I have two classes in my Rails application. One of them is Athlete and the other is AthleteGameLog. The interesting part of this relationship is that over the course of many years an athlete has_many instances of an AthleteGameLog. In the context of the current season however, the Athlete should have only one game log per year. So, one for 2013, one for 2014, etc.
When working with Ruby on Rails, is the has_many relationship still here the right way to go about this? Do I have to write a scope in order to retrieve the correct relationship for the specific year I am looking for all AthleteGameLog instances on?
A has_many relationship sounds just fine. You might want to add a validation that only allows one AthleteGameLog per year per Athlete which wouldn't be too hard.
You could write a scope if you choose that takes a year as an input and returns the correct game log. Something like.
Athlete.game_log_for_year(2013) for example which will return the correct game log.
Yes, it is definitively still has_many relationship. You can write parametrized scope to retrieve correct log:
class AthleteGameLog < ActiveRecord::Base
scope :for_year, ->(year) { find_by(year: year) }
end
athlete.game_logs.for_year(2014)
For those unhappy with scope not returning association proxy:
class AthleteGameLog < ActiveRecord::Base
def self.for_year(year)
scoped.find_by(year: year)
end
end
How about:
class Athlete < ActiveRecord::Base
# columns: id, <athlete columns>
has_many :athlete_game_logs
end
class AthleteGameLog < ActiveRecord::Base
# columns: id, <gamelog columns>, year
belongs_to :athlete
validates :athlete_id, :uniqueness => { scope: :year }
end
It is worthy add some unique index or unique constraint on athlete_id and year.
Using Rails 4 (and Postgres), I'm trying to work out the best way in which to structure my ActiveRecord associations and corresponding database tables/relationships for a diet tracking app.
I want my app to be structured as follows:
I have Document and FoodEntry models. Each Document has a number of FoodEntries. I want to be able to iterate over them like document.food_entries.each ... (which is easy with your typical has_many association).
However all the FoodEntries for each Document need to be able (potentially but not necessarily) to be subdivided by day, as this is a natural division for which logic and calculations must be able to be performed, in addition to doing them for the whole document. For instance I'd be using something like document.day(1).food_entries.each ....
Furthermore, each day should be able to be subdivided (again, optionally) into meals in a similar manner, e.g. document.day(1).meal(1).food_entries.each ...
Lastly, there must be a way to record the user-specified order that the FoodEntries, meals, and days are in for each document. Presumably using number sequence(s)?
I was thinking there are a few ways I could do this:
Use a simple has_many relationship. Have day, meal and sort columns in the food_entries table, where the value for day and meal is left blank or given a default value if a day/meal isn't provided. Use a logic-based approach to get and sort the entries for a day or meal.
Outline:
class Document
has_many :food_entries
class FoodEntry
belongs_to :document
Potential issues:
This might leave things a bit messy in general in the table?
All the logic for subdividing things would have to be hand-coded.
Storing/using the user-defined (i.e. arbitrary) sort order might get a bit complicated? The order for entries AND days AND meals would have to stored in and inferred from one sequence (unless more columns were added).
Use has_many :through to set up associations through days and meals (naming?) tables. Entries where a day/meal isn't specified get given a default. Both these tables have their own individual sort column, along with the food_entries table.
Outline:
class Document
has_many :days
has_many :meals, through: :days
has_many :food_entries, through: :days (AND :meals???)
class Day
belongs_to :document
has_many :meals
has_many :food_entries, through: :meals
class Meal
belongs_to :day
has_many :food_entries
class FoodEntry
belongs_to :meal
Potential issues:
Adds unnecessary relational complexity? (consider that days or at the very least meals are meant to be optional)
Can I even use has_many :food_entries through: ... in my Document model if it would have to go through both tables?
A compromise between the two approaches above: have a days table but keep meal in a column in the food_entries table.
Something else? Polymorphic association(s)?
This is getting a bit complicated to wrap my head around, and so I'm really having a hard time working out what I should use. What is the correct way to go about things?
A couple of final questions which are related but completely optional:
Ideally the day value could be either a datetime value or an arbitrary string, depending on what the user sets. Is this possible?
Could anyone point me to a resource that can inform me about sorting/ordering strategies? Like I said I assume the simplest way is to use a sequence of numbers, but I'm not exactly sure how I would work with such a sequence.
has_many :through
You'd only use a has_many :through relationship if you wanted to attribute multiple FoodEntries to Document, like this:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries_types
has_many :food_entries, through: :food_entries_types
end
#app/models/food_entry_type.rb
Class FoodEntryType < ActiveRecord::Base
belongs_to :document
belongs_to :food_entry
end
#app/models/food_entry.rb
Class FoodEntry < ActiveRecord::Base
has_many :food_entries_types
has_many :documents, through: :food_entries_types
end
This would only allow you to associate many food_entries with a similar number of documents. Although you could add specific days & meals attributes to the join model, allowing you to call them as required
Scopes
I believe a much better option for you is to use ActiveRecord scopes:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries
#uses [wday][3]
#needs to return dates for specific day
scope :day, ->(day = 1) { where(created_at: Date::DAYNAMES[day]) }
scope :meal, ->(meal = 1) { where(meal: meal) }
end
Because scopes can be chained, I believe you'd be able to do this:
food = Document.day(1).meal(2).food_entries
Class Method
You could also create a class_method to achieve something similar:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries
def self.sorted(day = 1, meal = 1)
document = self.where("created_at = ? AND meal = ?", Date::DAYNAMES[day], meal)
end
end
#app/controllers/documents_controller.rb
def show
#document = Document.sorted
end
I ended up implementing my #2 option. This has given me the most flexibility and worked out well. I think it has been the most elegant approach for my use case.
In my Rails (3.2) app, an Order has many LineItems. A LineItem has many LineItemPayments. A LineItemPayment has one Payment. (LineItems can potentially be payed for multiple times (subscriptions), which is why I have the join table there.)
I need to be able to query for order information from a payment record. I can get an array of orders via relationships, but I know they will always be the same order. Is there a way in Rails to set up the association to reflect this? If not, would it better to set up a method for retrieving the array and then picking the order out of that, or rather just storing the order_id with the payment and set up a direct relationship that sidesteps all this?
You'll need to work with the orders collection and narrow it down accordingly per your own logic. Although you certainly 'can' add the order_id to the payment directly, that will denormalize your data (as a cache) which is only recommended when you start hitting performance bottlenecks in your queries - otherwise it's asking for trouble in the area of data integrity:
class Payment < ActiveRecord::Base
has_many :line_item_payments
has_many :line_items, :through => :line_item_payments
has_many :orders, :through => :line_items
# use this to get the order quickly
def order
orders.first
end
# use this to narrow the scope on the query interface for additional modifications
def single_order
orders.limit(1)
end
end
class LineItemPayment < ActiveRecord::Base
belongs_to :line_item
belongs_to :payment
end
class LineItem < ActiveRecord::Base
belongs_to :order
has_many :line_item_payments
end
I am in the planning stages of building an app where all users (registered or not) can vote every minute or so. The voting window should last a set period of time (for ex. 1 month). At which point a winning entity is defined and the voting period resets and starts all over. Visitors can then leave comments about the winner for that period. My question is what do you think is the best way to set something like this up?
Here are my current thoughts, but doesn't seem ideal:
1) Vote model: entity_id, contest_id, user_id (optional), created_at, ip_address
search for ip in db on new vote and see if time diff greater than allowed vote time limit between user votes
use CAPTCHA every variable number of votes to ensure human
calculate current vote count by counting all the entries for an entity for a contest
2) Contest model: start and end datetime
have a weekly or monthly cron job create the newest instance
votes find current contest if current date in between these 2 dates
individual model allows to create attributes to the contest(for example, special kinds of contests)
3) Winner model: contest_id, entity_id
allows for users to comments on past contest winners
Without knowing more details, I would go with something along the lines of:
class User
has_many :votes
has_many :comments
has_many :contests, :through => :votes
class Vote
belongs_to :user
belongs_to :contest
class Contest
has_many :votes
has_many :users, :through => :votes
class Comment
belongs_to :user
This way, you can have #user.votes, #contest.votes, #contest.users, etc.
I don't see the need for a Winners model, since that can just be a boolean in Users. If you needed to, you could always have a Winnings model that belonged to both Users and Contests to link the two.
Hope that helps.