Rails Unconventional DB Structure, is this problematic for associations? - ruby-on-rails

My company's old team leader had fairly little knowledge of Ruby on Rails and used weird convention for the db structure for associations.
Let's say I have a User model and a Role model, and that a user belongs to a role.
What I would consider typical:
rails g migration AddRoleToUsers role:references
# app/models/user.rb
belongs_to :role
# app/models/role.rb
has_many :users
But I can clearly see that something else has happened.
Whenever I User.new, I get something like:
#<User stuffs, role_id: nil>
In the models I've got
# app/models/user.rb
belongs_to :role, :class_name => 'Role', :foreign_key => :role_id
# app/models/role.rb
has_many :users
This pattern litters a good part of our application. I can tell that these columns are index, but don't actually have a foreign keys.
First question, is this problematic? To me, this implies referential integrity issues.
Second question, how can I add a foreign key after the fact and preserve existing data?
I'm using a gem called data-migration, which I hope can help me preserve the information.

This is just a verbose way of writing what Rails will do automatically by convention.
In the User.role relationship, this:
belongs_to :role, :class_name => 'Role', :foreign_key => :role_id
is equivalent to this:
belongs_to :role
Rails can infer the class_name and foreign_key based on the name of the relationship. It's just a redundancy that won't hurt in this case.
The foreign_key is actually the name of the field that's used to store the id value for the relationship, it's not a database foreign key at all.
You can add a database foreign key for these relationships by creating a migration or each table that you want to update. There's a great answer for that in Adding foreign key to a rails model that gives the step-by-step instructions to do it.

Related

Mapping relationships with custom names in Rails Admin

I am using rails admin, and it works fine except for a pretty big issue. I have custom relationships in mongoid, and rails admin is not allowing me to edit that field.
My relationship definition in mongoid:
class Content
belongs_to :lesson, :inverse_of => :reading_material, class_name: "Lesson"
class Lesson
has_one :reading_material, :inverse_of => :lesson, class_name: "Content"
Note that it is a rather straight forward relationship. But this is what I see this in rails admin:
Surely there must be a way to tell rails admin what to do here?

Rails belongs_to returns nil class

I am trying to link two tables to each other
class Musers < ActiveRecord::Base
# Email
# sid (student_id:integer)
# isyk: boolean
belongs_to :user, :foreign_key => "smail"
end
class Users < ActiveRecord::Base
belongs_to :muser, :foreign_key => "email"
end
But,
#user = Users.first
#user.muser returns nil
By saying :foreign_key => "smail" you are telling rails that the Muser column smail points to the User model's foreign key. This is most likely not the case.
Assuming that the primary key of the User models is called id, you should a user_id field to Muser, and change belongs_to :user, :foreign_key => "smail" into:
belongs_to :user
On the User model you can define the reverse relation using:
has_one :muser
Also, to follow rails model naming conventions, you should rename Users to User and Musers to Muser.
You should read more about belongs_to and has_one.
If, on the other hand, the User model in fact uses email for it's primary key, I would strongly advise you to change that and add an auto-incrementing primary key instead. As a rule of thumb, the primary key should be chosen such that it never changes. If it does change, all foreign keys pointing to that primary key will have to change as well.
You should only use a non auto-incrementing primary key if you have a specific reason for doing so.
More information on choosing a primary key: How to choose my primary key?
Well you can't just tell rails the type of association, you actually have to set the association to an instance of that class. For example, making a new muser will not automatically assign a user as the belongs_to. You could do something like
u = User.new
u.muser = Muser.first
u.save
However, I'm not sure what you are trying to accomplish with a belongs_to - belongs_to relationship, but you should know that you have to do more than just tell rails it exists.

Calling created Objects in Rails Scaffold

I am new to Rails and creating a football results app, I did a rails generate Scaffold Team name:string form:string then I added a few teams to the table, my next step I tried was to create a Fixtures table that stores teams, so I did rails generate Scaffold Fixture week:string homeTeam:team awayTeam:team homeScore:integer awayScore:integer when I tried to update the database doing a rake db:migrate I am getting an error undefined method:team I understand rails does not like the way I specify them teams as type team.
How can I go about getting this to work, as when creating a fixture I want to be able to choose from a list of teams already stored in a teams table?
As a random aside, the convention in ruby/rails is to use underscores as opposed to camelCase for variables and methods.
On to your actual question! You need to setup relationships yourself in the generated Team and Fixture models. Scaffolding can help you setup the relationships though by getting the correct foreign keys in place.
For the Fixture scaffold, generate it like this:
rails g scaffold fixture week:string home_team_id:integer away_team_id:integer home_score:integer away_score:integer
Note that g is a shortcut for generator and the generator doesn't need anything to be capitalized.
Now, in your Team model you'll want to define your relationship to the Fixture and vice versa (I'm no sports expert, but wouldn't naming it Game make more sense?):
class Team < ActiveRecord::Base
has_many :home_games, :class_name => Fixture, :foreign_key => :home_team_id
has_many :away_games, :class_name => Fixture, :foreign_key => :away_team_id
end
class Fixture < ActiveRecord::Base
belongs_to :home_team, :class_name => Team
belongs_to :away_team, :class_name => Team
end

Rails Migrations - Alternative name for a foreign key?

In my application I have 3 tables, users, lists, tasks.
Users and lists is a many-to-many relationship, tasks belong to lists, and users can complete tasks.
For my migration, when a user completes a list I'd like to store who it was completed by in the database but I'm unsure how to do this.
I could simply add t.integer :user_id to my tasks migration, though I'd like to refer to it as .completed_by. Something that references the :user_id in my User table but is named :completed_by?
As MrDanA mentioned, you can specify a different foreign_key, but I would recommend against it. If you can, your db structure will be more understandable at the low level if you stay with entity name conventions.
In the future, it'll be immediately obvious that user_id points at the user table, while a new programmer won't be able to tell that completed_by points at the user table without exploring code. If you do want to have a specific name, or need more than one user association, something like "completed_user_id" remains clear at both the app and db levels.
You can then add methods like:
has_one :user
has_one :completed_by, :class_name => "User", :foreign_key => "completed_user_id"
or
has_one :user
def completed_by
self.user.id
end
etc.
In your migration, add t.integer :completed_by
Then, in your model, instead of:
has_one :user
do:
has_one :user, :class_name => "User", :foreign_key => "completed_by"
or even customize the association name, like:
has_one :completer, :class_name => "User", :foreign_key => "completed_by"

HABTM - uniqueness constraint

I have two models with a HABTM relationship - User and Role.
user - has_and_belongs_to_many :roles
role - belongs_to :user
I want to add a uniqueness constraint in the join (users_roles table) that says the user_id and role_id must be unique. In Rails, would look like:
validates_uniqueness_of :user, :scope => [:role]
Of course, in Rails, we don't usually have a model to represent the join relationship in a HABTM association.
So my question is where is the best place to add the constraint?
You can add uniqueness to join table
add_index :users_roles, [ :user_id, :role_id ], :unique => true, :name => 'by_user_and_role'
see In a join table, what's the best workaround for Rails' absence of a composite key?
Your database will raise an exception then, which you have to handle.
I don't know any ready to use rails validation for this case, but you can add your own validation like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :roles, :before_add => :validates_role
I would just silently drop the database call and report success.
def validates_role(role)
raise ActiveRecord::Rollback if self.roles.include? role
end
ActiveRecord::Rollback is internally captured but not reraised.
Edit
Don't use the part where I'm adding custom validation. It kinda works but there is better alternatives.
Use :uniq option on association as #Spyros suggested in another answer:
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :uniq => true, :read_only => true
end
(this code snippet is from Rails Guides v.3). Read up on Rails Guides v 3.2.13 look for 4.4.2.19 :uniq
Rails Guide v.4 specifically warns against using include? for checking for uniqueness because of possible race conditions.
The part about adding an index to join table stays.
In Rails 5 you'll want to use distinct instead of uniq
Also, try this for ensuring uniqueness
has_and_belongs_to_many :foos, -> { distinct } do
def << (value)
super value rescue ActiveRecord::RecordNotUnique
end
end
I think that using :uniq => true would ensure that you get no duplicate objects. But, if you want to check on whether a duplicate exists before writing a second one to your db, i would probably use find_or_create_by_name_and_description(...).
(Of course name and description are your column values)
I prefer
class User < ActiveRecord::Base
has_and_belongs_to_many :roles, -> { uniq }
end
other options reference here

Resources