Calculate average of column value in Ruby on Rails - 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)

Related

How do I get Rails to accept decimals?

I am creating some table data in a Rails-React application.
I had creating this piece of data here in console:
2.3.3 :024 > Crop.create date: Date.today, cropname: 'Radishes', ismetric: false, bagspackaged: '20', unitweight: '0.5', totalweight: '10'
Today I realized that Rails did not accept the 0.5 decimal for unitweight and no matter how I try to update it in console, it does not save.
This is my schema.rb file:
ActiveRecord::Schema.define(version: 20171004224716) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "crops", force: :cascade do |t|
t.date "date"
t.string "cropname"
t.boolean "ismetric"
t.integer "bagspackaged"
t.integer "unitweight"
t.integer "totalweight"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Two issues here
First you have given the data type integer to unitweight and totalweight, while you should have given it decimal or float in order to accept and store fractions. decimal data type with precision is better as it will give you more accurate result as stated below in the comments' section.
when you use decimal you can control it by The precision which is total number of digits in a number, whereas scale is number of digits following the decimal point.
here is an example
add_column :tickets, :price, :decimal, precision: 5, scale: 2
this will allow you to store decimal numbers like these 60.00, 80.99 and 100.00
Second you are passing string to integer, it is not a problem because rails will convert it to integer as long as it is a valid integer otherwise it will be 0. But generally it is not a good practice.
I would avoid rolling back your crops table, it would just be more work. It is up to you.
I would just do:
rails g migration ChangeUnitweightToFloat
Inside that file I would configure like so:
class ChangeUnitweightToFloat < ActiveRecord::Migration
def change
change_column :crops, :unitweight, :float
end
end
With these two steps, you should be go to go.
For future reference, please keep in mind that if you want to work with decimals, it will either be a t.decimal or t.float.
That isn't a decimal, it's a string. Don't put quotes around your numeric literals.
Crop.create(
date: Date.today,
cropname: 'Radishes',
ismetric: false,
bagspackaged: 20,
unitweight: 0.5,
totalweight: 10
)
You could use a decimal (or float) type field instead of an integer:
create_table "crops", force: :cascade do |t|
t.decimal "unitweight"
end
and then don't use quotes around the value:
2.3.3 :024 > Crop.create date: Date.today, cropname: 'Radishes', ismetric: false, bagspackaged: '20', unitweight: 0.5, totalweight: '10'

Organising data for a football (soccer) management simulation

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.

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

Is there a way to pass property.method to .where() instead of property in Rails?

My Schedule model looks like this:
create_table "schedules", force: :cascade do |t|
t.integer "week_day"
t.time "opening_time"
t.time "closing_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "taco_place_id"
end
add_index "schedules", ["taco_place_id"], name: "index_schedules_on_taco_place_id"
As you can see, there are opening_time and closing_time properties and I have a realtionship Schedule belongs_to :taco_place and TacoPlace has_many :schedules, dependent: :destroy.
What I am trying to do from the Schedule model is to get the actual schedule for a TacoPlace for today (if it exists).
I have already implemented a scope for having today's schedules for a TacoPlace (depending on the week_day property) that looks like this:
scope :today_for_taco_place, ->(taco_place){where(taco_place_id: taco_place.id, week_day: Time.now.wday)}
and I'm using it in this method:
def self.actual_for_taco_place(taco_place)
today = self.today_for_taco_place(taco_place)
today.where("opening_time <= :now and closing_time >= :now", now: Time.now.utc).first
end
I have tested it and it "works". The thing is that if I run "Schedule.first.opening_time" on the console I get "2000-01-01 06:00:00 UTC". As you can see, it does not only include the time, but also the day (even if it was seeded as "opening_time: "15:00".to_time, closing_time: "24:00".to_time").
Finally, here is the question:
Is there a way that I can run something like this: (I know this won't work yet)
def self.actual_for_taco_place(taco_place)
today = self.today_for_taco_place(taco_place)
today.where("#{opening_time.strftime("%H%M")} <= :now and #{closing_time.strftime("%H%M") >= :now", now: Time.now.utc.strftime("%H%M")).first
end
So that the .where() method doesn't look for the property (opening_time or closing_time), but rather perform the strftime() method so I can compare the time only? Or should I save the opening_time and closing_time as integers (i.e. "1200") or manually convert them in a method?
Sorry if my question was long or hard to understand. Thank you in advance for your advise.
Opening_time and closing_time are now integers. I figured out that I don't gain anything from it being a "time" instead of an "integer" since it is only representing an hour.

Why is my floating point rounded in Rails?

I have a model that uses floating points to store geolocations. It stores the data in MySQL, using ActiveRecord, Rails 3.2.x.
c = Camping.new()
c.latitude=51.77802459999999
c.save
c.latitude
=> 51.77802459999999
c.reload
c.latitude
=> 51.778
When looking in the database, I confirm it is stored as 51.778. Other numbers are stored with more precision, I have the idea the 0 is what makes either ActiveRecord or MySQL decide to omit the rest.
What decides that the number could or should be chopped off after three digits? Is there anything to control this? Is float the correct type to store this in?
I want a precision of 6, never more, but could be less, or padded. So when 51.7780 is provided, it might be stored as 51.7780 or as 51.778000. When 51.77802459999999is provided, it could be stored as 51.778025 or as the number with full precision; I don't really care.
Relevant part from schema.rb:
create_table "campings", :force => true do |t|
#...
t.float "latitude"
t.float "longitude"
end
Apparently, MySQL is the problem, as float is described as an approximated value.
You can use decimal instead, and specify the precision and scale:
create_table "campings", :force => true do |t|
#...
t.decimal "latitude", :precision => 15, :scale => 10
t.decimal "longitude", :precision => 15, :scale => 10
end

Resources