Multiple associations to the same model in Rails - ruby-on-rails

Let's say I have a model Dogs and each dog has exactly 2 Cat "enemies", enemy1 and enemy2, how do I write the migration file such that I can call dog.enemy1 to retrieve the first enemy and dog.enemy2 to retrieve the second enemy?
I tried this:
create_table :dog do |t|
t.string :name
t.timestamps null: false
end
add_index :dog, :name
add_foreign_key :dogs, :cats, column: :enemy1_id
add_foreign_key :dogs, :cats, column: :enemy2_id
end
I also tried it with the t.references method but could not get it to work. Been working on this problem for hours. and it works fine in development but not on Heroku Postgres.
The error i get is
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:column "enemy1_id" referenced in foreign key constraint does not exist.
Any ideas?

Migration:
create_table :dog do |t|
t.string :name
t.integer :enemy1_id
t.integer :enemy2_id
t.timestamps null: false
end
Dog Model:
class Dog < ActiveRecord::Base
has_one :enemy1, class: 'Cat', foreign_key: :enemy1_id
has_one :enemy2, class: 'Cat', foreign_key: :enemy2_id
end

There is not a 'has_two' association in rails. So you should settle with has_many association.
class Dog < ActiveRecord::Base
has_many :cats, limit: 2
scope :enemy1, Proc.new { |object| object.cats.first }
scope :enemy2, Proc.new { |object| object.cats.last }
end
in class cat
class Cat < ActiveRecord::Base
belongs_to :dog
end
Now the migration for create cats should have
t.references :dog
You need not have foreign fields in your dog model. This should solve your problems.

The Rails Way should not be to do this in a migration. Migrations, in my opinion, is a tool to define the structure your data would live in.
The Rails Way would suggest you have a has_many association on the Cat class for Dogs
class Dog < ApplicationRecord
# ... other code ...
has_many :enemies, class_name: Cat
# ... other code ...
end
You'd also have to define the belongs to association in Cat
class Cat < ApplicationRecord
# ... other code ...
belongs_to :dog
# ... other code ...
end
Note that the cats table should be defined to have a reference to the dogs table. So your migration for the cats table should read something like
class CreateCats < ActiveRecord::Migration[5.x]
create_table :cats do |t|
# ... other code ...
t.references :dog
# ... other code ...
end
end
With these setup, you should then define enemy_one and enemy_two in your Dog class
class Dog < ApplicationRecord
# ... other code ...
def enemy_one
enemies.first
end
def enemy_two
enemies.second
end
# ... other code ...
end
To make things even stricter, you may decide to add a validation (on create) which checks that only two Cats are ever created as enemies per Dog. Moreso, you could hide the creating button (or general access) when a Dog has reached its limit for enemies. This, I leave to your discretion.
p.s: All these constrictions could also be done in the database layer. But the application layer is here to abstract all that out.

I just solved it independently, 2 seconds after the first answer came in..
Finally.
class CreateDogs < ActiveRecord::Migration
def change
create_table :dogs do |t|
t.string :name
t.references :enemy1, index: true
t.references :enemy2, index: true
t.timestamps null: false
end
add_index :dogs, :name
add_foreign_key :dogs, :cats, column: :enemy1_id
add_foreign_key :dogs, :cats, column: :enemy2_id
end

Related

Is it advisable to use :foreign_key in my migrations rather than just adding user_id?

I am using rails 4.2, I just want to know if there would be any difference if I use the :foreign_key keyword in my migrations rather than just adding a user_id column to add relationship to my models ?
YES
The key difference is not on the application layer but on the database layer - foreign keys are used to make the database enforce referential integrity.
Lets look at an example:
class User < ActiveRecord::Base
has_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
If we declare things.user_id without a foreign key:
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :user_id
t.timestamps null: false
end
end
end
ActiveRecord will happily allow us to orphan rows on the things table:
user = User.create(name: 'Max')
thing = user.things.create
user.destroy
thing.user.name # Boom! - 'undefined method :name for NilClass'
While if we had a foreign key the database would not allow us to destroy user since it leaves an orphaned record.
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
user = User.create(name: 'Max')
thing = user.things.create
user.destroy # DB says hell no
While you can simply regulate this with callbacks having the DB enforce referential integrity is usually a good idea.
# using a callback to remove associated records first
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
end

Rails: incrementing attribute from a model, upon creation of an instance from another model

Our Rails app works with the following models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are our migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
...
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
When a new #calendar is created, we need to increment :total_calendar_count and :owner_calendar_count by one in the User table.
We tried this in the CalendarsController:
class CalendarsController < ApplicationController
def create
#calendar = current_user.calendars.create(calendar_params)
current_user.total_calendar_count += 1
current_user.owned_calendar_count += 1
current_user.administrations.find_by(calendar_id: #calendar.id).update(role: 'Creator')
...
end
But it does not seem to update :total_calendar_count and :owner_calendar_count by one in the User table.
Are we missing a step here? Should we use an update action instead?
The actual problem in your code is that you don't then save the user.
So you update the counter... but this changes it on the local instance... and then after the controller action is done the change you made just disappears.
if you wanted to keep your code the way it is, you could do:
current_user.save
at the end.
but I'd advise you to look into the counter_cache, because it's the Rails way.
Also I'll point out that you haven't checked that the calendar successfully got created, before incrementing that counter... it's possible that it could fail a validation and not really have been created... you need to check for that first.
I have a best idea to solve your problems is as below....
Create a method that will call on the creating of calendar with the callbacks of model like as below...
Add the below inside the calendar model just after the validation and ORM relations
after_create :increment_counter
def increment_counter
calendar_user = self.user
calendar_user.update(:total_calendar_count += 1, :owned_calendar_count += 1 )
end
With the above code you don't need to do anything. It will increment the counter of calendar on every new entry of calendar.

Advice for designing a relationship in Rails

I need to connect 2 players with a connecting table. That connecting table must have an active flag, and possibly more attributes going forward. I want to be able to associate 2 Players to the PlayerLink an active_player and passive_player, so that I may say player_link.active_player.name.
I have tried the following but am not getting the behavior I want. Perhaps I am missing something silly, or approaching it incorrectly.
module V1
class Player < ActiveRecord::Base
belongs_to :player_link
end
end
module V1
class PlayerLink < ActiveRecord::Base
has_one :active_player, class_name: "Player", foreign_key: :active_player_id
has_one :passive_player, class_name: "Player", foreign_key: :passive_player_id
end
end
schema.rb entry:
create_table "player_links", force: true do |t|
t.integer "active_player_id"
t.integer "passive_player_id"
t.boolean "active"
t.datetime "created_at"
t.datetime "updated_at"
end
Test query that blows up:
V1::PlayerLink.where(active: true).each do |l|
l.active_player.update_attribute(:foo, bar)
l.passive_player.update_attribute(:foo, bar
end
With the error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: players.active_player_id: SELECT "players".* FROM "players" WHERE "players"."active_player_id" = ?
Bonus: Why is Rails querying agains players?
These are called self-referential associations - I had trouble the first time I did them as well.
Try modifying your controller logic as follows:
module V1
class Player < ActiveRecord::Base
has_many :player_links
end
end
module V1
class PlayerLink < ActiveRecord::Base
belongs_to :player, :foreign_key => "active_player_id"
belongs_to :passive_player, :class_name => "Player", :foreign_key => "passive_player_id"
end
end
For more info, see RailsCast #163.

Rails Relational Database not working how I want

In the console
a = Reported.new
This works. After tinkering.
a.profile = Profile.first
But it's not what I want! I want a.profile to even exist. I want a.reported_by to be a profile! And I want a.reported to be a profile!
Again what I want is
a.reported_by = Profile.last #or any such profile
a.reported = Profile.first #or any such profile
Model
class Profile < ActiveRecord::Base
has_many :reported, dependent: :destroy
Migration
It doesn't have a reported column, I am not sure about the right way to implement that either.
class CreateReporteds < ActiveRecord::Migration
def change
create_table :reporteds do |t|
t.belongs_to :profile
t.integer :reported_by
t.string :reason
t.timestamps
end
end
end
Your migration seems... off. I've never seen t.belongs_to :something in a migration - shouldn't it be t.integer :profile_id? (I couldn't find documentation supporting the belongs_to syntax there).
If you want Reported#reported_by to return a Profile, then you need a reported_by_id integer on it, NOT a reported_by integer. Rails has a convention where you should make your referenced objects (in this case, a belongs_to :reported_by relationship) use the relationship_id format for it's foreign key.
Then you should have this in your class:
class Reported < ActiveRecord::Base
belongs_to :reported_by, class_name: "Profile"
end
This will make it so it uses reported_by_id as the foreign key for a Profile object, but return it as Reported#reported_by.
Then:
class Profile < ActiveRecord::Base
has_many :reporteds, foreign_key: 'reported_by_id'
end
Should let you do Profile#reporteds
And your migration would look like this:
class CreateReporteds < ActiveRecord::Migration
def change
create_table :reporteds do |t|
t.integer :reported_by_id
t.string :reason
t.timestamps
end
end
end

Rails - Model association seems to be saving, but not loaded during find()?

In one model, I have this:
class Game < ActiveRecord::Base
has_one :turn
attr_accessor :turn
attr_accessible :turn
default_scope :include => :turn
def Game.new_game
turn = Turn.create count: 1, phase: 'upkeep', player: 1
game = Game.create turn: turn
game
end
end
class Turn < ActiveRecord::Base
belongs_to :game
end
Later, in a controller, I have this:
respond_with Game.find(params[:id])
But for some reason, the returned game has a turn_id that is nil and no associated turn object.
Why isn't the association being saved properly, or not returning properly with find()?
In my migration, I think I've setup the association correctly:
create_table :games do |t|
t.timestamps
end
def change
create_table :turns do |t|
t.string :phase
t.integer :count
t.references :game
t.timestamps
end
end
You seemed to have got messed up on associations
This is what i Think as per the understanding of scenario.
The associations should be like
class Game < ActiveRecord::Base
has_one :turn
#.....
end
class Turn < ActiveRecord::Base
belongs_to :game
#.....
end
and migrations like
create_table :games do |t|
#add some columns
t.timestamps
end
create_table :turns do |t|
t.references :game
#add some columns
t.timestamps
end
now to add new game and turn
game = Game.create
turn = game.turn.create count: 1, phase: 'upkeep', player: 1
game.tun.create will automatically create a turn record with game_id = game.id and other supplied args.
problem with your migration is game is referring turn which instead should be opposite .
Find more on associations here
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://guides.rubyonrails.org/association_basics.html
Something is inverted.
Since Turn has the belongs_to statement, Turn should contain game_id, and not the other way around.
So you can't access a Game turn_id because the field does not exists. It will be always nil and if ou remove the has_one statement it will raise an exception.

Resources