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.
Related
I'm sure my wording isn't great which explains why I'm failing at searching. I'm trying to work out how to identify records that are related to two others. Example will make it clearer.
Model Contest has a has_many relation to Results. The results have a Team number(but lets use letters for Clarity)
So in this example I'm trying to find all the Contests that Team A and Team D have both attended.
I want to get back a enumerator of all the Contests that fit this condition so I can then compare the two teams to each other.
I apologize for this not being the best write up, I'm struggling for the terms to define what I'm trying to do. Thank you for your help, time and patience!
Given:
class Contest < ApplicationRecord
has_many :results
has_many :teams, through: :results
end
class Result < ApplicationRecord
has_many :teams
belongs_to :contest
end
class Team < ApplicationRecord
belongs_to :result
delegate :contest, to: :result
end
This will set up your associations, and let you access Contest from Team. The delegate method says "if I call team.contest, do team.result.contest instead".
Hope that helps!
EDIT
And if you're wanting to gather the contests:
Contest.joins(results: :teams).where(teams: { id: team_a.id })
Contest.joins(results: :teams).where(teams: { id: team_b.id })
The problem is you would need to join the same table twice, once with team A results and once with team B results. You need to reference them as separate tables. You can use Arel and assign unique names to the two instance of the table.
contests_between = Arel::Table.new(:contests, as: 'contests_between')
team_a_results = Arel::Table.new(:results, as: team_a_results)
team_b_results = Arel::Table.new(:results, as: team_b_results)
relation = contests_between.project(Arel.sql('*')).
join(team_a_results).on(contests_between[:id].eq(team_a_results[:contest_id])).
join(team_b_results).on(contests_between[:id].eq(team_b_results[:contest_id]))
And then you can do...
relation = relation.where(team_a_results[:team_id].eq(1)).where(team_b_results[:team_id].eq(2))
#contests = relation.to_sql
# above assumes team_a is id 1 and team_b is id 2... if the teams are already loaded you could substitute team_a.id and team_b.id
I'm building a small sports app where there are years, weeks, games and a user's picks. I built it for 2014 and now for 2015, I'm working my way back that it easily works with any year.
Currently, when I call user.picks, it returns all of their picks for both 2014 and 2015. I only want 2015 but I'm unsure to alter the structure to get just that. I'm sure it's a simple solution.
Is it possible to do without creating a scope on pick? Ideally I'd like to call user.picks than user.picks.for_this_year.
Here's my db structure:
User.rb
has_many :picks
Week.rb
has_many :games
has_many :picks
Week has attribute on it, :year
Game.rb
belongs_to :week
has_many :picks
Game has function year which looks at parent week's year
Pick.rb
belongs_to :user
belongs_to :game
belongs_to :week
Has function year which look's at parent game's year that it gets from parent week
Thanks for your help!
I would argue against removing the association between User and Pick as you will find yourself creating numerous work arounds to accommodate what is a fundamental relationship in your application.
You could set up a default_scope
class Pick < ActiveRecord::Base
default_scope {joins(:week).where("weeks.year = ?", Time.now.year)}
end
A named scope is the preferred way and you could provide a conditional argument
class Pick < ActiveRecord::Base
def self.for_year(year=Time.now.year)
joins(:week).where("weeks.year = ?", year)
end
end
You would have to call user.picks.for_year or user.picks.for_year("2014") but your code is clearer and more maintainable. Default_scopes aren't necessarily bad but it can be easy to forget that one is created, as well as also leading to some messy workarounds.
You could remove the has_many :picks from User model and create a custom function picks in your User Model.
class User < ActiveRecord::Base
...
def picks
Pick.joins(:User, :Week).where("users.id = ? and weeks.year= ?",self.id, Time.now.year).distinct
end
end
I am relatively new to ruby/rails and I have the following question:
I am working on a scheduling app and have a model named Classes and another named ClassEntries. The relationship between them is that each user can have multiple class entries per semester, each relating to one class. Each record in the Classes table belongs to a specific University. A User can have multiple entries in the ClassEntries table for 1 semester (typically 5). Their schedule is comprised of all their ClassEntries with the same semester ID.
I am not sure whether I should have a third model called Schedule that brings together the info in the ClassEntries and Classes models for the user at hand. I originally wrote this functionality in PHP and I simply used a MySQL JOIN to gather the necessary information. In Rails it seems that there should be a better way to accomplish this.
What would be the best way of going about this in Rails?
Many thanks
So, what you are looking for is pretty much associations in Rails.
You would have the following:
def User < ActiveRecord::Base
has_many :course_entries
has_many :courses, :through => :class_entries
end
def CourseEntry < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
def Course < ActiveRecord::Base
has_many :course_entries
has_many :users, :through => :class_entries
end
With those associations set up, Rails would allow you to do such things like
some_user.courses or some_course.users and it will make the joins through CourseEntry for you.
Let me know if this helps. If you need me to go more in depth let me know.
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.
I'm a beginning to ROR, but here's what I'm trying to achieve. I have two items I want to associate: matters and people. Each matter can have many people. That is, I want to create people and matters separately and later be able to link them.
For example, I may create:
Bill Clinton
Barack Obama
I may create the matters:
Global warming
War on terror
I want to be able to associate the users Bill Clinton AND Barack Obama to BOTH matters. Can someone point me to a tutorial that can show me how to do this?
I think has_and_belongs_to_many is used less and less by the RoR community now. While still supported, I think it is now more common to have an intermediate model (in your case something like PoliticianMatter) to join your Politician and Matter models.
Then your politician_matter table will have a PK, a politician_id and a matter_id.
Then you have
class PoliticanMatter < ActiveRecord::Base
belongs_to :politician
belongs_to :matter
end
The advantage of this approach is that if there ever need to be future properties of the politician -> matter relationship (e.g importance, date of last occurrence) you have a model which affords this - has_and_belongs_to_many would not support the addition of these extra properties.
You can also access the many to many relationship directly from the Politician and Matter models like this
class Politician < ActiveRecord::Base
has_many :politician_matters
has_many :matters, :through => :politician_matters
end
class Matter < ActiveRecord::Base
has_many :politician_matters
has_many :politicians, :through => :politician_matters
end
You need a many2many relationship between these two entities.
A matter can be studied by many people
A person can studie several matters
Rails uses the has_and_belongs_to_many helper to do that. You'll find more about that in the documentation and many many blog posts!
has_and_belongs_to_many helper
class Politician < ActiveRecord::Base
has_and_belongs_to_many :tasks
end
class Task < ActiveRecord::Base
has_and_belongs_to_many :politicians
end
What you need are 3 tables:
politicians, tasks and politicians_tasks (having the two columns politician_id and task_id, no primary key)
Hope this helps
Seb