Table belonging to two models? - sql

I want to have a model for a Baseball team that has many players and statistics about the team.
I also want a model for players that have the same statistics.
How can I have the Statics table belong to both the Baseball Model and the Player Model?

You can use Polymorphic association. You can refer here
Statistic model
class Statistic < ActiveRecord::Base
belongs_to :statisticable, polymorphic: true
end
Baseball model
class Baseball < ActiveRecord::Base
has_many :statistic, as: :statisticable
end
Player model
class Player < ActiveRecord::Base
has_many :statistic, as: :statisticable
end
Your migration file for Statistic model
class CreateStatistic < ActiveRecord::Migration
def change
create_table :statistics do |t|
t.string :strength
t.string :defensive
t.string :attack
t.references :statisticable, polymorphic: true, index: true
t.timestamps null: false
end
end
end

Related

Can I have two foreign key from one table to an other, in order to get two columns field?

I have two tables one Player and the other Result. Can I set two foreign key on Result, related to Player, so that I can change the value of an two players with the Result? I am working on Ruby web application where I will update the fields for each Player according to the results I am going to add that are related to a football game. For example, if the score is Player1 3 - 0 Player2, Player1 will get 3 points while the other 0, so basically from one result I will have to change two fields from the table Player. I will also have to add the GoalsScored and GoalsTaken by each player. Can I sort this thing out assigning for example two names in result, I will check the name in the Player table and then change the fields, using two foreign keys, or I need just to set my methods correctly?
Here are the tables:
Player
t.string :name
t.integer :win
t.integer :draw
t.integer :lose
t.integer :gs
t.integer :gt
t.integer :dr
t.integer :points
Result
t.string :name1
t.integer :goal1
t.string :name2
t.integer :goal2
t.datetime :date
t.references :player, null: false, foreign_key: true
Is it ok if I add two references to Result?
Yes, you can. And sometimes that's a good idea, but not here. You want a join table instead. Here's why.
You need to use two different column names and tell Rails what table they reference.
Result
t.string :name1
t.integer :goal1
t.string :name2
t.integer :goal2
t.datetime :date
t.references :player1,
null: false,
foreign_key: { to_table: :players }
t.references :player2,
null: false,
foreign_key: { to_table: :players }
And you'd need to make them explicit in your code.
class Result < ApplicationRecord
belongs_to :player1, class_name: 'Player'
belongs_to :player2, class_name: 'Player'
end
Tying them together in Player is a little tricker. The naive thing to do is this.
class Player < ApplicationRecord
has_many :player1_results,
class_name: 'Result',
foreign_key: :player1_id
has_many :player2_results,
class_name: 'Result',
foreign_key: :player2_id
end
What if you want all the Player's results? What if you want all the players of a Result? There's the problem. You need to make redundant queries or add extra clauses like where player1_id = :player_id or player2_id = :player_id. Similarly name1, name2, goal1, goal2.
Whenever you want to store more than one associated thing, you need a join table. Even if it's only two. It makes life much easier.
You have a result table, but a result of what? A match! Where's information about the match stored? In the result table. It should be its own table.
We have three tables. Players, Matches, and a table to store how a player did in a match (the result).
create_table :players do
t.string :name, null: fase
...
t.timestamps
end
create_table :matches do
t.string :name, null: false
t.datetime :date, null: false
...
t.timestamps
end
create_table :results do
t.references :player, foreign_key: true, null: false
t.references :match, foreign_key: true, null: false
t.integer :goals
end
Now with all three pieces, we can put them together. The Matches and Players are related through Results.
class Matches < ApplicationRecord
has_many :results
has_many :players, through: :results
end
class Players < ApplicationRecord
has_many :results
has_many :matches, through: :results
end
class Results < ApplicationRecord
belongs_to :match
belongs_to :player
end
Now if you want to find the players in your match...
players = match.players
This will perform the join on results for you.
If you must track who is player 1 and player 2, add that to the Result table with a unique constraint.
create_table :results do
t.references :player, foreign_key: true, null: false
t.references :match, foreign_key: true, null: false
t.integer :goals, null: false, default: 0
t.integer :player_number, null: false, default 1
# Can't have two player 1s for the same match.
# :player_number is deliberately first so this index also serves
# to index player_number.
t.index [:player_number, :match_id], unique: true
end
Then you can get player 1 like so:
player = match.players.find_by!(player_number: 1)
And you can add some convenience methods to the relationship.
class Matches < ApplicationRecord
has_many :results
has_many :players, through: :results do
def player(num)
match.players.find_by!(player_number: num)
end
end
end
player = match.players.player(1)

How to select records with zero associations from two nested has_many?

Some days ago I asked the following question: How to select records with zero associations from two has many?
I failed to adapt the answer that solved my previous question to this similar situation:
class Report < ApplicationRecord
belongs_to :departure, class_name: 'Stop'
belongs_to :arrival, class_name: 'Stop'
end
class Stop < ApplicationRecord
has_many :departure_reports, foreign_key: 'departure_id', class_name: 'Report'
has_many :arrival_reports, foreign_key: 'arrival_id', class_name: 'Report'
belongs_to :journey
end
class Journey < ApplicationRecord
has_many :stops
end
These are the corresponding migrations:
class CreateReports < ActiveRecord::Migration[5.2]
def change
create_table :reports do |t|
t.references :departure, foreign_key: { to_table: :stops }
t.references :arrival, foreign_key: { to_table: :stops }
t.timestamps
end
end
end
class CreateStops < ActiveRecord::Migration[5.2]
def change
create_table :stops do |t|
t.references :journey, foreign_key: true
t.timestamps
end
end
end
class CreateJourneys < ActiveRecord::Migration[5.2]
def change
create_table :journeys do |t|
t.timestamps
end
end
end
Here is a more visual description:
How to select journeys whose stops don't have any associated report as departure or arrival ?
What I'm looking for would be equivalent to this:
Journey.all.select do |journey|
journey.stops.all? do |stop|
stop.departure_reports.count + stop.arrival_reports.count == 0
end
end
Edit
Trying the solution proposed by Sebastian Palma:
Journey
.left_joins(:stops)
.joins('LEFT OUTER JOIN reports ON stops.id IN (reports.departure_id, reports.arrival_id)')
.where(reports: { departure_id: nil, arrival_id: nil })
I get the following sql query:
SELECT "journeys".* FROM "journeys"
LEFT OUTER JOIN reports ON stops.id IN (reports.departure_id, reports.arrival_id)
LEFT OUTER JOIN "stops" ON "stops"."journey_id" = "journeys"."id"
WHERE "reports"."departure_id" IS NULL AND "reports"."arrival_id" IS NULL
LIMIT $1 [["LIMIT", 11]]
With this error:
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "stops")
LINE 1: ...eys".* FROM "journeys" LEFT OUTER JOIN reports ON stops.id I...
^
Have following model side changes,
class Journey < ApplicationRecord
has_many :stops
has_many :departure_reports, through: :stops
has_many :arrival_reports, through: :stops
end
And get journeys having both
Journey.where.not(id: Journey.joins(:departure_reports, :arrival_reports).distinct.ids)
Good part of above is, only single query is fired even inner attributes are passed by calling another query.

Rails associations has_many through ban/archive solution?

I'm new in Rails and am working on a problem. I have two tables:
Shoes and Socks
A Shoe can have many Socks, but only one active Sock. Other Socks are inactive. All Socks are also unique with unique patterns. I want to make sure I don't have socks with the same pattern. I think I can do this three ways
1) Using an additional column in table socks to represent the active sock
class Shoe < ActiveRecord::Base
has_many :socks
end
class Sock < ActiveRecord::Base
belongs_to :shoe
end
class CreateGettingDressed < ActiveRecord::Migration
def change
create_table :shoes do |t|
t.string :size
t.timestamps null: false
end
create_table :socks do |t|
t.belongs_to :shoe, index:true
t.string :pattern
t.boolean :active
t.timestamps null: false
end
end
end
This seems fairly simple, but cumbersome. I would search socks with shoe_id, and pull out the active_sock and return it's pattern. I think I would index [active_sock, shoe_id] in an array to speed it up.
2) Using an additional table to archive inactive socks
class Shoe < ActiveRecord::Base
has_many :socks
has_many :inactive_socks
end
class Sock < ActiveRecord::Base
belongs_to :Shoe
end
class Inactive_sock < ActiveRecord::Base
belongs_to :Shoe
end
class CreateGettingDressed < ActiveRecord::Migration
def change
create_table :shoes do |t|
t.string :name
t.timestamps null: false
end
create_table :socks do |t|
t.belongs_to :shoe, index:true
t.string :pattern
t.timestamps null: false
end
create_table :inactive_socks do |t|
t.belongs_to :shoe, index:true
t.string :pattern
t.timestamps null: false
end
end
end
This seems cumbersome as well, but when you are just dealing with active socks easy to use and fast. But when buying a new sock, I have to check the pattern with both tables.
3) Using a has_many :through relationship
class Shoe < ActiveRecord::Base
has_many :active_socks
has_many :socks, through: active_socks
end
class Active_Sock < ActiveRecord::Base
belongs_to :Shoe
belongs_to :Sock
end
class Sock < ActiveRecord::Base
has_many :active_socks
has_many :shoes, through: active_socks
end
class CreateGettingDressed < ActiveRecord::Migration
def change
create_table :shoes do |t|
t.string :name
t.timestamps null: false
end
create_table :socks do |t|
t.string :pattern
t.timestamps null: false
end
create_table :active_socks do |t|
t.belongs_to :shoe, index: true
t.belongs_to :sock, index: true
t.string :pattern
t.boolean :active
t.timestamps null: false
end
end
end
This seems like option 2, but I feel like I'm using Rails tools to make it less cumbersome. When I'm searching for patterns I'm just checking the socks table, when I'm searching for the one active_sock I'm just searching active_socks table.
I've read up on similar posts, and it seems options 1 and 2 are commonly used in closed_accounts, banning users, banning posts, archiving etc. Situations where you need to differentiate data that is only slightly different. The choice there seems to be look at what you need and choose the option 1 or 2 that best fits you.
My understanding for has_many through situations seems to be when you have a relationship and you need extra meta data you can use it. I think that fits this situation.
Did I set up option 1 correctly and am I right that indexing the array of [shoe_id and active] will give me a faster search? Is option 3 an appropriate use of has_many through? Would my explanation of option 3 work?
What are your usage patterns? I'm guessing you just want to be able to find the active Sock given a Shoe, and if a given Sock is active or inactive. To quickly find the active Sock of a given Shoe, you merely need to give the Sock a foreign key to its Shoe with the belongs_to association.
class Sock < ActiveRecord::Base
belongs_to :shoe
end
And to find out if a Sock is active or inactive, give its owner shoe a reference to its active sock like so:
class Shoe < ActiveRecord::Base
belongs_to :sock
end
Now, you can just go to its owner Shoe and check if the Shoe's active sock is the current Sock or not. E.g.
def is_active
owner_shoe.active_sock == self
Basically, associate them with foreign keys and you're good to go!
P.S. you pluralized Socks but the Rails convention is to use singular for model names.
EDIT: You asked for a migration so here's one for the code above. Caveat: I haven't done migrations in a long time in Rails so something might be off.
class CreateGettingDressed < ActiveRecord::Migration
def change
create_table :shoes do |t|
t.belongs_to :active_sock, foreign_key: "sock_id"
t.string :size
t.timestamps null: false
end
create_table :socks do |t|
t.belongs :owner_shoe, foreign_key: "shoe_id"
t.string :pattern
end
end
end

Change primary and foreign key in ActiveRecord

I have two models :
class Settlement < ActiveRecord::Base
set_primary_key :settlement_identifier
has_many :streets
attr_accessible :city, :name, :service_available, :zip, :country_id,: settlement_identifier
end
class Street < ActiveRecord::Base
belongs_to :settlement, foreign_key: "settlement_identifier"
attr_accessible :name, :settlement_identifier, :street_identifier
end
Because I am doing import for streets and settlements, I need to point streets via settlement_identifier, not settlement_id .
When I do
Street.first.settlement #it compare settlement_identifiers from both tables
But when try to get streets from single settlement like :
Settlement.first.streets
It throws an error
SELECT "streets".* FROM "streets" WHERE "streets"."settlement_id" = 4263
ActiveRecord::StatementInvalid: PG::Error: ERROR: column streets.settlement_id does not exist .
I want that query to be :
SELECT "streets".* FROM "streets" WHERE "streets"."settlement_identifier" = 4263
Any help ?
I solved this problem. Here is solution below :
class CreateSettlements < ActiveRecord::Migration
def change
create_table :settlements, primary_key: :settlement_identifier, id: :false do |t|
t.string :name
t.string :zip
t.string :city
t.string :service_available
t.integer :country_id
t.timestamps
end
end
def down
drop_table :settlements
end
end
Here I set primary_key in my migration to settlement_identifier, and set id to false
Also, my Street migration is:
class CreateStreets < ActiveRecord::Migration
def change
create_table :streets do |t|
t.string :name
t.integer :settlement_identifier
t.string :street_identifier
t.timestamps
end
end
end
So, Street has reference to Settlement via settlement_identifier .
Settlement model :
class Settlement < ActiveRecord::Base
has_many :streets, foreign_key: "settlement_identifier"
attr_accessible :city, :name, :service_available,:settlement_identifier
end
Street model :
class Street < ActiveRecord::Base
belongs_to :settlement, foreign_key: "settlement_identifier"
attr_accessible :name, :settlement_identifier, :street_identifier
end
I tried to set primary_key on Settlement model but that didn't work.
This works fine for me. If anyone have another solution, please put comment or code example.

Rspec does not load the objects through has_and_belongs_to_many association

I have this in my model:
class Instance < ActiveRecord::Base
has_and_belongs_to_many :owners, :class_name => 'User'
and this:
class User < ActiveRecord::Base
has_many :instances
and I have this migration:
class CreateInstancesUsersJoinTable < ActiveRecord::Migration
def up
create_table :instances_users, :id=>false do |t|
t.string :instance_id
t.string :user_id
end
end
def down
drop_table :instances_users
end
end
And in instance controller I have:
#instance.owners << owner
but the tests says that owner is not into owners array. But when I say:
p #instace.owners - before or after #instance.owners << owner
the test passes. Does anyone know why this happens?
In User model you should write
has_and_belongs_to_many :instances
instead of
has_many :instances