how to relate this data? - ruby-on-rails

In my app, a subject has_many goals and each goal belongs to a subject.
This relationship makes the most sense, but I'm wondering how to use it in practice. For example - after a given time frame, each subject's goals should be evaluated, but they'll be evaluated on a student-by-student basis.
so let's say
goal.1 = "drink an entire gallon of milk without vomiting"
student.1 = "Lisa"
And let's also say Lisa is really quite good at holding her bile - so for goal.1, Student.1 = 5. Goals are tied to subjects, so I know that goal.1 belongs to English, Math, etc, but what's the best way to model the relationship that goals have to students when the students are being evaluated? They should also be able to be evaluated several times in a given period.

I think the following covers your description:
Subject
has_many :goals
end
Goal
belongs_to :subject
has_many :evaluations
end
Student
has_many :evaluations
end
Evaluation
belongs_to :goal
belongs_to :student
# columns: score, date
end
The evaluation object allows a student to have many evaluations for many goals over a period of time.

Related

How do you identify models that contain related records in Ruby on Rails?

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

Rails ActiveRecord: Where conditions on second query of Includes

So I have four tables, users, courses, projects, and grades. courses has many projects, which has many grades. I can't give the whole classes, but here's the appropriate associations.
class Course < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :course
has_many :grades
end
class Grade < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
class User < ActiveRecord::Base
has_many :grades
end
I would like to list out all courses, and within each course I can see all their projects, and their grade for each project. (Keep in mind what I am giving you here is simplified from my actual project.)
So obviously I should use an includes() statement to make this as efficient as possible. Here's what it would look like:
Course.includes(projects: [:grades]).all
This gives me the course, projects, and grades, separated among three small, efficient queries. This is exactly what I want but the killer issue here is that I only want grades for the current user. So I would normally go
Course.includes(projects: [:grades]).where(grades: [user_id: current_user.id])
However, this turns the fast slender three queries into one large, slow query with multiple joins. Normally this would be ok, but in my real implementation there is much more going on in the query and adding more joins makes that one query's runtime explode. So I would like to keep it broken up into smaller queries, such that the where statements for grades and projects are added to their queries, instead of thrown into one giant join. Is there any way to do this?

How should I design/structure my database and ActiveRecord associations? has_many :through?

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.

Structure for recurring unlimited voting contests

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.

Ruby On Rails Relationships - One to Many

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

Resources