I am new with Rails and I would need some advice :) I have this:
class Club < ActiveRecord::Base
has_many :players, :through => :club_players
has_many :club_players
end
class Player < ActiveRecord::Base
has_many :clubs, :through => :club_players
has_many :club_players
end
class ClubPlayer < ActiveRecord::Base
belongs_to :club
end
create_table "players", force: true do |t|
t.string "name"
t.string "age"
t.string "nationality"
t.string "sex"
end
Now I would like to model search engine where users can search players by age and have that defined like this in drop down:
Team Seniors
Team Kids
Age [14-18]
Age [19-25]
Age [26-30]
Age [31-35]
Age [36-40]
Retired
Or by nationality:
American
Indian
French
Mixed
How can my search engine look like if user clicks on Age [19-25]? Where would be a good idea to define what certain string in drop down represent? The same thing with nationality. For example I need to define somewhere what category A Team Seniors represent. I would define that team as a team which has more than 50% of players older than 25 years. So, I need somehow to define categories which are defined from some columns of Player.
What would be the best practice in doing this? Thanks!
I would create Tags for each of these categories. Then you could apply as many/little of the tags you want to apply to them. Or you create some look up tables/lists for the different sections like nationality.
For age, you probably need birthday and you could then create an age method to give you their current age.
Related
I'm creating a simple app using the SWAPI and I'm struggling with the model creation.
Let's take the People resources from the API (without Films), for example:
Attributes:
name string -- The name of this person.
birth_year string -- The birth year of the person, using the in-universe standard of BBY or ABY - Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is a battle that occurs at the end of Star Wars episode IV: A New Hope.
eye_color string -- The eye color of this person. Will be "unknown" if not known or "n/a" if the person does not have an eye.
gender string -- The gender of this person. Either "Male", "Female" or "unknown", "n/a" if the person does not have a gender.
hair_color string -- The hair color of this person. Will be "unknown" if not known or "n/a" if the person does not have hair.
height string -- The height of the person in centimeters.
mass string -- The mass of the person in kilograms.
skin_color string -- The skin color of this person.
homeworld string -- The URL of a planet resource, a planet that this person was born on or inhabits.
species array -- An array of species resource URLs that this person belongs to.
starships array -- An array of starship resource URLs that this person has piloted.
vehicles array -- An array of vehicle resource URLs that this person has piloted.
url string -- the hypermedia URL of this resource.
created string -- the ISO 8601 date format of the time that this resource was created.
edited string -- the ISO 8601 date format of the time that this resource was edited.
So I was thinking about the following model for Rails:
class CreatePeople < ActiveRecord::Migration[6.1]
def change
create_table :people do |t|
t.string :name
t.string :birth_year
t.string :eye_color
t.string :gender
t.string :hair_color
t.string :height
t.string :mass
t.string :skin_color
t.references :homeworld, null: false, foreign_key: { to_table: :planets }
t.references :species, null: false, foreign_key: true, array: true, default: []
t.text :starships, array: true, default: []
t.timestamps
end
end
end
Since what I need is actually an array with the URLs of homeworld, species and starships, I'm not sure if I adding the id from each (from t.references) is the proper way to add each field.
Can I get only the URL from each, even though I don't have a id referencing it?
Does the "array: true, default: []" syntax work properly for what I need, as an array?
starships, species and vehicles are many to many relations.
You can solve this by an array column and some custom logic or via a "join table". The latter is, IMHO, preferred and what DBs are good at.
The associations can be stup approx. like this:
class Starship < ApplicationRecord
has_many :starship_possessions
has_many :people, through: :starship_possessions
...
end
class StarshipPossession < ApplicationRecord
belongs_to :starship
belongs_to :person
end
class Person < ApplicationRecord
has_many :starship_possessions
has_many :starships, through: :starship_possessions
...
end
and the StarshipPossession table at least has id, 'starship_id' (FK) and person_id (FK) but could, depending business requirements and data also contain information about start/enbd date of ownership and so on.
When importing a Person into your DB then you can do following steps for each starship URL
Check if the starship exists or if not, create it
def find_starship(starship_url)
starship = Starship.find_or_initialize_by(url: starship_url)
return starship if starship.persisted?
# it's a new starship, fetch data and update
starship_data = fetch_starship_data_via_api(starship_url)
starship.some_property = starship_data.some_property # depending on the data structure returned you could just use 'update'
starship.save!
starship
end
Associate it to the person (perhaps first check if it is not associated yet)
StarshipPossession.create!(starship: starship, person: person)
# or
person.starships.create!(starship: starship)
# or
starship.people.create!(person: person)
Obviously this is written without having a running example of the code, so there might be some problems (e.g. in my use of people vs person).
There are some things to note:
I assume that a Person can own a starship only once. So you should probably add a unique key to the starship_possessions table for [person_id,starship_id]
I used the bang (!) versions for save! and create! to raise an exception when something goes wrong, so I notice immediately when there is an error
We're creating a flight scheduling program that schedules employees and planes for flights.
Here are the models that currently exist
Airport
Pilot
FlightAttendant
Aircraft
We are starting with only four airports. We want to fill in the four airports in the Airports table as soon as the app launches, and then make a self-join table from the Airports that lists all possible origin-destination combinations and their duration.
How would we do that?
I've seen some stuff online about it but it looks like its done when we create the models, but our models are already made, so I can't figure out the migration and how to fill in that table automatically. We know the durations and just need to feed them in.
EDIT:
In response to the flight times: we plan to store them as an integer which is the number of minutes it takes to complete a flight. The airports are in Midwestern cities chosen at random in the Central Time Zone.
Lincoln and Iowa City: 32 mins
Lincoln and Evanston: 57 mins
Lincoln and West Lafayette: 62 mins
Iowa City and Evanston: 24 mins
Iowa City and West Lafayette: 31 mins
Evanston and West Lafayette: 13 mins
For further detail
This is the specific migration which created the Airports table
class CreateAirports < ActiveRecord::Migration[5.2]
def change
create_table :airports do |t|
t.string :full_name
t.string :flight_code
t.timestamps
end
end
end
full_name is simply the name like Evanston. flight_code is the the three letter code to represent it, like EVA.
The model is currently empty. Do I need to add something in it first before I add the association columns, or do I need to generate the migration to create the join table and then alter the Airport model?
You can run ruby code after the migration, Rails code in this case:
class CreateAirports < ActiveRecord::Migration[5.2]
def change
create_table :airports do |t|
t.string :full_name
t.string :flight_code
t.timestamps
end
Airport.create(params_for_airport1)
Airport.create(params_for_airport2)
Airport.create(params_for_airport3)
Airport.create(params_for_airport4)
end
end
Then, for the durations table, I imagine you'll have something like a "FlightDuration" using a table with 3 columns: airport_from_id, airport_to_id, duration. You can do the same as before, you create the table and right after the create_table just create the objects with the data.
You'll have to add the relationships to Airport and to FlightDuration, something like:
class Airport < ApplicationRecord
has_many :flight_durations_from, class_name: 'FlightDuration', inverse_of: :airport_from
has_many :flight_durations_to, class_name: 'FlightDuration', inverse_of: :airport_to
end
class FlightDuration < ApplicationRecord
belongs_to :airport_from, class_name: 'Airport', foreign_key: :airport_from_id
belongs_to :airport_to, class_name: 'Airport', foreign_key: :airport_to_id
end
I have a model Place.
For instance, place can be a city, a district, a region, or a country, but I think one model will be enough, because there is no big difference between city and region for my purposes.
And places can belong to each other, e.g. a region can have many cities, a country can have many regions etc.
But my problem is that it's kind of historical project and a city can belong to many regions. For example, from 1800 to 1900 a city belonged to one historical region (that doesn't exist now) and from 1900 till now - to another. And it's important to store those regions as different places despite of they can have similar geographical borders but only names are different.
I guess it's many-to-many, but maybe someone can give a better idea?
And if it's many-to-many, how can I get a chain of parent places in one query to make just simple string like "Place, Parent place 1, Parent place 2, Parent place 3", e.g. "San Francisco, California, USA"?
Here is my code:
create_table :places do |t|
t.string :name
t.timestamps null: false
end
create_table :place_relations, id: false do |t|
t.integer :sub_place_id
t.integer :parent_place_id
t.timestamps null: false
end
class Place < ActiveRecord::Base
has_and_belongs_to_many :parent_places,
class_name: "Place",
join_table: "place_relations",
association_foreign_key: "parent_place_id"
has_and_belongs_to_many :sub_places,
class_name: "Place",
join_table: "place_relations",
association_foreign_key: 'sub_place_id'
end
Please, don't hesitate to give me some ideas about it!
This is the first solution that popped in my mind, and there may be many other ways to do it, but I believe this may arguable be the cleanest.
You've got the right general idea, but all you need is a slight modification to the join table. Essentially you'll use has_many... through relationships instead, so that you can append some kind of time frame discriminator.
In my examples, I'm using a datetime field to indicate from what point the association is relevant. In combination with a default scope to order the results by the time discriminator (called effective_from in my examples), you can easily select the "current" parents and children of a place without additional effort, or select historical data using a single date comparison in the where clause. Note that you do not need to handle the time frame discrimination as I did, it is merely to demonstrate the concept. Modify as needed.
class PlaceRelation < ActiveRecord::Base
default_scope { order "effective_from DESC" }
belongs_to :parent, class_name: "Place"
belongs_to :child, class_name: "Place"
end
class Place < ActiveRecord::Base
has_many :parent_places, class_name: "PlaceRelation", foreign_key: "child_id"
has_many :child_places, class_name: "PlaceRelation", foreign_key: "parent_id"
has_many :parents, through: :parent_places, source: :parent
has_many :children, through: :child_places, source: :child
end
and the migration for the place_relations table should look like this:
class CreatePlaceRelations < ActiveRecord::Migration
def change
create_table :place_relations do |t|
t.integer :parent_id
t.integer :child_id
t.datetime :effective_from
t.timestamps
end
end
end
So if we create a couple of "top level" country-places:
country1 = Place.create(name: "USA")
country2 = Place.create(name: "New California Republic")
and a state place
state = Place.create("California")
and a city
city = Place.create("San Francisco")
and finally tie them all together:
state.parent_places.create(parent_id: country1.id, effective_from: DateTime.now - 1.year)
state.parent_places.create(parent_id: country2.id, effective_from: DateTime.now)
city.parent_places(parent_id: state.id, effective_from: DateTime.now)
Then you would have a city ("San Francisco") which belongs to the state "California", which historically belonged to the country "USA", and later "New California Republic".
Additionally, if you would like to build a string containing the place's name and all its "parents", you could do it "recursively" like so:
def full_name(name_string = [])
name_string << self.name
parent = self.parents.first
if parent.present?
return parent.full_name name_string
else
return name_string.join(", ")
end
end
Which, in the case of our city "San Francisco", should result in "San Francisco, California, New California Republic", given the ordering of the effective_from field.
This makes a association of direct many to many relation with another model without intervening that model . But you can use more advanced stuff if you want like Polymorphic Association .
For More Information please visit Rails Guide : Polymorphic Association
Rails app:
A user has_many positions.
Each position has one company (company name and company id) per the following schema:
create_table "positions", :force => true do |t|
t.integer "user_id"
...
t.string "company"
t.integer "company_id"
end
I would like users to be able to "follow" as many individual companies as they would like (i.e. a user can follow many different companies a company can be followed by many different users). It would seem that this calls for a has_and_belongs_to_many relationship between users and positions, but I want users to be able to follow the company attribute of a position row and not the position itself.
Should I create a new "following" table altogether that would pull companies from the positions table to be matched to user_id's? Or is there a way I can set up a has_many :through relationship and map user_id's to company_id's?
Thank you!
What I think you could have
A User table:
integer User_Id
....
A Company Table:
string company
integer company_id
...
A Positions table:
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
A Following table (If the user can follow any comapny regarding of whether he has a position in it):
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
OR if the user can only follow a company that he has position in then you can add a new column to position table. This would be a boolean flag telling if the user if 'following' the company identified by the position. Alternatively the Following table can also map user to position in this case.
I broadly agree with MickJ, although having created the Company and User models/tables (which obviously have an id column in each) I'd do it as:
create_table "companies" do |t|
t.string "name"
...
end
create_table "positions" do |t|
t.references "user"
t.references "company"
...
end
create_table "followings" do |t|
t.references "user"
t.references "company"
...
end
Models:
class User
has_many :positions
has_many :followings
end
class Company
has_many :positions
has_many :followings
end
class Position
belongs_to :user
belongs_to :company
end
class Following
belongs_to :user
belongs_to :company
end
You could reference the company from the position by doing:
position = Position.first
puts position.company.name
or by user with something like
user = User.first
user.positions.each do |position|
puts position.company.name
end
-- EDIT1:
To extract the company name from positions into a separate table you'd be best off writing a little rake task - something like:
Position.all.each do |position|
company = Company.find_or_initialize_by_name(position.company_name)
position.company_id = company.id
position.save
end
Then you might want to write a migration to remove the company name column from the positions table ... just to keep things tidy.
I need some help over here to understand how the model relationship works on rails. Let me explain the scenario.
I have created 3 models.
Properties
Units
Rents
Here is the how relationship mapped for them.
Model #property.rb
class Property < ActiveRecord::Base
has_many :units
has_many :rents, :through=> :unit
end
Model #unit.rb
class Unit < ActiveRecord::Base
belongs_to :property
has_many :rents
end
Model #rent.rb
class Rent < ActiveRecord::Base
belongs_to :unit
end
here is the the schema
create_table "units", :force => true do |t|
t.integer "property_id"
t.string "number"
t.decimal "monthly_rent"
end
create_table "rents", :force => true do |t|
t.integer "unit_id"
t.string "month"
t.string "year"
t.integer "user_id"
end
OK, here is my problem. Let's say I have 2 properties
property A
property B
and
property A has unit A01,A02,A03
property B has unit B01,B02,B03
I need to generate a report which shows the SUM of all the outstanding rents based on the property and month
So here is how it should be looks like. (tabular format)
Property A - December - RENT SUM GOES HERE
Property B - December - RENT SUM GOES HERE
So I got all the properties first. But I really can't figure out a way to merge the properties and units (I guess we don't need the rents model for this part) and print them in the view. Can someone help me to do this. Thanks
def outstanding_by_properties
#properties = Property.find(:all)
#units = Unit.find(:all,:select=>"SUM(monthly_rent) as total,property_id",:conditions=>['property_id IN (?)',#properties])
end
I think something like this will work for you. Hopefully an SQL guru will come along and check my work. I'm assuming your Property model has a "name" field for "Property A," etc.--you should change it to whatever your field is called.
def outstanding_by_properties
Property.all :select => "properties.name, rents.month, SUM(units.monthly_rent) AS rent_sum",
:joins => { :units => :rents },
:group => "properties.id, rents.month, rents.year"
end
This should return an array of Property objects that have the attributes name, month, and rent_sum.
It basically maps to the following SQL query:
SELECT properties.name, rents.month, SUM(units.monthly_rent) AS rent_sum
FROM properties
JOIN units ON properties.id = units.property_id
JOIN rents ON units.id = rents.unit_id
GROUP BY properties.id, rents.month, rents.year
The JOINs connect rows from all three tables and the GROUP BY makes it possible to do a SUM for each unique combination of property and month (we have to include year so that e.g. December 2008 is not grouped together with December 2009).