Organising data for a football (soccer) management simulation - ruby-on-rails

I'm trying to create an online football (soccer) management game with Ruby on Rails, and as it's quite ambitious for me I'm finding some parts fairly challenging. I've coded a basic match engine, but when it comes to tactics, lineups, formations, etc. I'm finding it more difficult to organise the various data and create relations in ActiveRecord. The same applies to league and cup systems.
I'll try to provide a brief overview here:
a nation/club has a first team and a youth/u21 team
a nation/club/team has players
a nation/club/team has matches against others in league and cup systems
a league system has three leagues in each division (pyramid system: 1 promoted, 3 relegated)
a cup system has knockout matches (and occasionally mini-league group stages) including extra time and penalty shootouts
a league/cup has rounds/match days for each season
a round/match day has matches
a match has details e.g. scores/ratings
a match has actions e.g. goal/assist
a match has tactics/lineups for each team e.g. formation/players
Any ideas how best to organise this in models?
Edit: What I'm mainly having trouble with is linking players to matches (via lineups?). Both teams need 11 of their players selected to play: 1 in goal and the remaining 10 outfield players spread across the defence/midfield/attack outfield positions, e.g. 4-4-2, etc. So Player 11 could be chosen to play in midfield, Player 9 in attack, Player 1 in goal, etc. Possible formations include 3-5-2, 3-4-3, 4-4-2, 4-5-1, 4-3-3, etc.
Here's a sample of the schema I'm attempting to use:
create_table "teams", force: :cascade do |t|
t.integer "nation_id"
...
end
create_table "players", force: :cascade do |t|
t.integer "nation_id"
t.integer "team_id"
...
end
create_table "matches", force: :cascade do |t|
t.integer "home_team_id"
t.integer "away_team_id"
...
end
create_table "lineups", force: :cascade do |t|
t.integer "match_id"
t.integer "team_id"
...
end
create_table "formations", force: :cascade do |t|
t.string "name"
...
end
create_table "positions", force: :cascade do |t|
t.integer "formation_id"
t.string "name"
...
end
Would something like this work? I'm not sure if the formations or positions tables are neccessary, or if that would even work.

This is not a Rails question per se - it is a general modelling question. There should be a large number of books, articles and tutorials on objectoriented modelling out there.
Without going into much detail here:
Basically, you also did most of the work by writing out your list.
Every word that is a substantive in your description leads to a (candidate for a) model. I.e.: nation, club, team, player, match, league-system, cup, league, division, match-day etc.
Draw those in boxes (google "UML class diagram" if you want to do it fancy). Those boxes correspond to files app/models/*.rb.
Draw a line between each of the boxes that have some kind of relationship (a.k.a., association) between them.
Mark out how many of each can be related (i.e., "each player can have zero or one team", "each team can have many players" etc.). This gives you to your has_many, has_one and belongs_to associations.
At the end, look for models that are just too trivial to have their actual Rails class. For example, the "day" might or might not be class-worthy (i.e., it could simply be a date attribute for your matches; but if you want to associate more information with the day per se, or if you want to plan matches which occur on the same day without having an actual date yet during the planning phase, then go ahead). Much of this is a matter of style, opinion and experience, there are no hard and fast rules here.
Check out the classical work "Design Patterns" for the introduction into modelling.

Related

How to query on a method value?

Consider this table:
create_table "liquor_lots", force: :cascade do |t|
t.integer "recipe_id"
t.datetime "created_at", precision: 6, null: false
t.integer "counter"
end
And the resulting model
class LiquorLot < ApplicationRecord
def lotcode
"#{recipe_id}#{created_at.strftime("%y")}#{created_at.strftime("%W")}#{created_at.strftime("%u")}"
end
def pallet_lotcode
"#{lotcode}-#{counter}"
end
end
I'd like to do the equivalent of this in SQL:
Select distinct(lotcode) from liquor_lots
I've tried this and it understandably fails because lotcode is not a column on the liquor_lots table. But I've always been advised against adding columns to store data that is derived from data in other columns.
So how do I search for those values?
For context, my lotcode actually consists of many more values concatenated together, I just limited to three in the example for readability.
As far as I know, with basic ActiveRecord you cannot do that.
ActiveRecord would have to know too much about your ruby code.
You could implement a SQL query that concatenates the relevant values by hand (see comment to your question).
Or you can query all objects (or just the relevant values using pluck()) and then work on that with standard Ruby Array/Enumerable methods (in memory). If the application is not performance-critical, happens rarely, and you do not have thousands of the liquor_lots, that would be an okay productivity-tradeoff in my eyes.
Besides storing it in an own column, you could also extract the codes in separate table and make PalletLotcode an entity of its own. LiquorLots would than belong_to a single PalletLotcode which would have_many LiquorLots. But compared to the separate column this is a rather complex operation, but makes sense if other information is to be stored on the Lotcodes.
You can try something like:
LiquorLot.where("recipe_id = :rcp_id AND created_at >= :begin_of_day AND created_at <= :end_of_day", {begin_of_day: calculate_begin_of_day, end_of_day: calculate_end_of_date, rcp_id: id})
calculate_begin_of_day and calculate_end_of_date can be implemented using Date.comercial method and Date.beginning_of_day and Date.end_of_day

How sort records based on two fields with different weighs of relevance?

I have the following example:
#ads = Ad.all
I need to sort by popularity so it is based on two integer columns: contacts_count and visualizations.
I want to know if there is any of these options:
1) List first the ads with the most number of contacts and to the records with contacts_count = 0 show the records sorted by visualizations number.
2) Somehow attribute weighs to the two fields like: 5 to contacts and 3 to visualizations or something like that and sort by relevance by this.
How can I do this with active record or any search gem?
schema.rb
create_table "ads", force: :cascade do |t|
t.string "photo"
t.text "description"
t.string "category"
t.integer "user_id"
t.integer "visualizations", default: 0
t.integer "contacts_count", default: 0
t.index ["user_id"], name: "index_ads_on_user_id", using: :btree
end
From what I understood from your description, what you need is not searching but sorting.
Short answer:
#ads = Ad.order(:contacts_count, :visualization)
Wait but why?:
list first the ads with the most number of contacts
order first by contacts
to the records with contacts_count = 0 show the records sorted by visualizations number.
This statement is a bit inaccurate, you will use visualizations as a tiebreaker (even in the case of contacts_count = 0 or just equal contacts_count.

Calculate average of column value in Ruby on Rails

I'm kind of new to Ruby on Rails. I have a profile model which has_many courses taken.
create_table "profiles", force: :cascade do |t|
t.string "pname"
t.float "current_gpa"
end
and
create_table "courses", force: :cascade do |t|
t.integer "course_number"
t.float "gpa"
end
I want to calculate the average gpa: current_gpa = sum of gpa of courses taken / num of course taken. How can I do this?
You should consider reading some documentation - obviously it's quick to get a answer on SO but sometimes the docs can lead you to something you didn't know to look for or ask for.
That said, the fastest way is to use average
profile.courses.average(:gpa)
This will give you an average. Or you can do it the long way, if for some reason you need make modifications in between.
profile.courses.sum(:gpa) / profile.courses.count
An additional note here... Average returns floating point numbers or BigDecimal which is often represented in decimal notation. This can be confusing and may not be what you are looking for. You might explore adding something like: .to_int, .to_f, .truncate, .round, etc...
Person.average(:age) # => 0.387e2
Person.average(:age).to_i # => 38
Person.average(:age).to_f # => 38.7
Person.average(:age).to_f.round # => 39
You can use ActiveRecord::Calculations average:
profile.courses.average(:gpa)

Permutating an existing array to seed a Rails database

I would like to seed my Rails app database with the permutation of an existing array of objects, and am unsure about the best way to go about this.
I currently have a Country model, with the following attributes:
create_table :countries do |t|
t.string :name
t.float :latitude_dec
t.float :longitude_dec
t.timestamps null: false
end
I have seeded this model from a .yaml file (as these attributes are static), and now would like to use these records to seed a CountryPair model (where the attributes are also static). This model will have the following attributes:
create_table :country_pairs do |t|
t.string :country_a
t.string :country_b
t.string :pair_name
t.float :country_a_latitude_dec
t.float :country_b_latitude_dec
t.float :country_a_longitude_dec
t.float :country_b_longitude_dec
t.float :distance
t.timestamps null: false
end
The aim is to permutate the array of Country objects, and create a CountryPair object from each permutation (and seed the database with the output). I understand the Ruby array#permutation method, but am unsure about how to pull out the appropriate values into the new array of CountryPair objects. The order of countries in the pair is important here, so I'd like to use permutations rather than combinations.
Ultimately, I'd also like to calculate the distance between the country pairs, but I'm hoping to start figuring that out once I have the CountryPair model filled!!
This is my first foray back into Rails after a five year absence, so apologies if I've got some of the terminology/methodology wrong - please do ask for clarification if any further information is required! Thanks in advance!
You can add this snippet to your seeds.rb after the Countries are seeded.
Country.all.permutation(2) do |p|
CountryPair.create(
country_a: p[0].name,
country_b: p[1].name,
pair_name: p[0]name + p[1].name,
country_a_latitude_dec: p[0].latitude.dec,
country_b_latitude_dec: p[1].latitude.dec,
country_a_longitude_dec: p[0].longitude.dec,
country_b_longitude_dec: p[1].longitude.dec,
distance: # An algorithm to calculate distance
)
end
Then run it with: rake db:setup

Rspec rails testing - join table not working

(I replicated an isolated example of my issue on github: https://github.com/diingo/jobs_emps. It contains just the problematic portion - no views, no controllers, just the models I describe below and the breaking test.)
My code is working on development and production but currently a portion is breaking only in the test environment leaving me unable to test it properly.
Here's what's happening:
I have two models, jobs and employees. Each job has employees that oversee and participate in it as a specific role (employee_type) - Manager or Exective. Employees can be promoted - a manager can be promoted to an executive. But their roles for previous jobs they participated in must remain the same.
A join between employees and jobs (JobsEmployee) keeps track of the employee's role through the employee_type attribute. The join will not update a user's position if it was previously set - this is done with a before_save, as seen here:
class JobsEmployee < ActiveRecord::Base
before_save :set_user_type
def set_user_type
self.user_type ||= self.user.type
end
end
This works fine in actual use on development and production. If a job is created, with a manager and exective, job.jobs_employees will show one manager and one executive. If a manager is promoted to executive and that job is then updated for whatever reason, job.jobs_employees will still show one manager and one executive.
However, during testing this changes. Upon updating a job, if an employee was promoted, job.jobs_employees shows two executives.
My test is shown below. You can see I abstracted controller create and update methods into models for convenience. You can reference them on the github link: https://github.com/diingo/jobs_emps/blob/master/app/models/job.rb
RSpec.describe JobsEmployee, :type => :model do
before do
#job_permitted_params = {
city: "Munich",
status: "in_progress"
}
#manager = Employee.create!(name: "Bob Bobber", type: 'Manager')
#executive = Employee.create!(name: "Alice Smith", type: 'Executive')
#job_raw_params = {
job: {
manager_id: #manager.id,
executive_id: #executive.id
}
}
end
it "creates and updates" do
job = Job.create_with_params(#job_permitted_params, #job_raw_params)
# This passes:
expect(job.jobs_employees.map &:employee_type).to include("Manager", "Executive")
#manager.type = 'Executive'
#manager.save!
Job.update_with_params(job, #job_permitted_params, #job_raw_params)
# This breaks in testing but works in production:
expect(job.jobs_employees.map &:employee_type).to include("Manager", "Executive")
end
end
I put break points (pry debugger) in JobsEmployee#set_user_type to see what might be happening. It appears like the record in JobsEmployee are deleted before or during a Job update. So instead of seeing that self.user_type is already set in self.user_type ||= self.user.type, it just runs self.user.type again.
Here is the schema. You can also see it in the github link.
ActiveRecord::Schema.define(version: 20150301232938) do
create_table "employees", force: true do |t|
t.string "name"
t.string "type"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "jobs", force: true do |t|
t.string "city"
t.string "status"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "jobs_employees", force: true do |t|
t.string "employee_type"
t.integer "employee_id"
t.integer "job_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
I'm not sure why this is happening. Thanks so much for checking out the problem.
The "actual use" you are describing may not correlate with your test. Updating a job and changing an employee's role would not necessarily cause the wholesale reassignment of jobs_employees association that you are doing in your test. I suspect that when you are doing that, Rails is comparing the set of JobsEmployee records represented by the ids you are assigning to the currently associated records. Since the currently associated records have the user_type field set, they aren't equivalent to the records that would be created upon a fresh assignment, so they are deleted and new ones regenerated.

Resources