Ruby on Rails: Using Foreign Key twice in the same table? - mysql

I have two models:
class Word < ApplicationRecord
has_many :g_words, class_name: 'Translation', foreign_key: 'g_id'
has_many :v_words, class_name: 'Translation', foreign_key: 'v_id'
end
class Translation < ApplicationRecord
belongs_to :g, class_name: 'Word', required: true
belongs_to :v, class_name: 'Word', required: true
end
Table Translations
t.text "note", limit: 65535
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "g_id"
t.integer "v_id"
In table Words I already inserted 2 values:
id body
1 Home
2 Maison
When I create a new Translation with
g_id v_id
1 2
Then the following error appears.
The search a lot on the internet about my problem and here is a post about what I want to achieve exactly: http://www.emreakkas.com/ruby-on-rails/rails-multiple-columns-to-the-same-tables-key
I tried to implemented and failed.
I don't know whether I implented the associations wrong or I declared the wrong foreign Keys. I really don't know where to start finding the error. I hope you can help me! Thank you!

If you are looking to build a translations table this is how you would do it:
class Word < ApplicationRecord
has_many :translations
has_many :languages, through: :translations
end
class Language < ApplicationRecord
has_many :translations
has_many :words, through: :translations
end
class Translation < ApplicationRecord
belongs_to :language
belongs_to :word
end
But Rails already has a built in i18n API which handles translations that you should take a look at first before reinventing the wheel.

Related

Why isn't Ruby on Rails loading my associated objects with .includes()?

My Problem
I'm attempting to retrieve data through a foreign key association in my Ruby on Rails application. The data from the primary table is loaded correctly, but the associated objects are not being loaded and are always nil.
Background Info (Migrations, Database tables, and Model classes)
I'm currently working with two tables:
eval_forms
user_details
The tables are created through Rails migrations.
The user_details table is created through a single migration:
class CreateUserDetails < ActiveRecord::Migration[5.2]
def change
create_table :user_details do |t|
t.string :eduPersonPrincipalName, unique: true
t.string :DisplayName, default: 'NULL'
t.string :Email, default: 'NULL'
t.string :Role, default: 'Student'
t.boolean :hasAppointment, default: '0'
t.timestamps
end
end
def self.down
drop_table :user_details
end
end
and the eval_forms table has had a few migrations to create and update it:
class CreateEvalForms < ActiveRecord::Migration[6.1]
def change
create_table :eval_forms do |t|
t.belongs_to :form_builder, foreign_key: 'form_builder_id'
t.belongs_to :course, foreign_key: 'course_id'
t.string :Description
t.datetime :OpenDate
t.datetime :CloseDate
t.timestamps
end
end
end
class UpdateEvalForms < ActiveRecord::Migration[6.1]
def change
add_column :eval_forms, "Author_user_details_id", :bigint, null: false
add_foreign_key :eval_forms, :user_details, column: "Author_user_details_id"
add_column :eval_forms, "Year", :integer
add_column :eval_forms, "Semester", :string
add_column :eval_forms, "IsArchived", :boolean
end
end
I know that the foreign key is set up correctly as it is listed correctly in MySQL. Here's a reference from MySQL of the 2 tables and their relation:
Additionally, I've set up the model classes in my Rails app.
eval_form:
class EvalForm < ApplicationRecord
has_many :eval_forms_roles
has_many :roles, through: :eval_forms_roles
has_many :eval_forms_courses
has_many :courses, through: :eval_forms_courses
has_many :eval_responses
has_many :eval_reminders
belongs_to :user_detail
validates :formName, presence: true
validates :formData, presence: true
end
user_detail:
class UserDetail < ApplicationRecord
has_one :la_detail
has_many :eval_responses
has_many :eval_forms
end
So What's Wrong?
Lastly, here is the code to retrieve the objects from the database and the section where I'm getting my error.
My controller action:
def index
# list *all* existing evaluation forms, with options to filter by OpenDate, CloseDate, etc (todo)
#EvalForms = EvalForm.includes(:user_detail)
end
My view:
<td><%= ef.user_detail.DisplayName %></td>
My error:
NoMethodError in Evaluations::EvalForms#index
undefined method `DisplayName' for nil:NilClass
Extracted Source location: <td><%= ef.user_detail.DisplayName %></td>
Restating the problem
In conclusion, I'm really confused as to why the associated user_detail objects are not being retrieved despite my .includes() statement in the controller action. I'm pretty new to Ruby as well as Rails, but there are other sections in the application that look similar to this and work correctly so I don't see what my issue is.
I would start by using conventional naming which in Rails means snake_case everywhere:
class CreateUserDetails < ActiveRecord::Migration[5.2]
def change
create_table :user_details do |t|
t.string :edu_person_principal_name, unique: true
t.string :display_name
t.string :email
t.string :role, default: 'Student'
t.boolean :has_appointment, default: false # let the driver handle conversion
t.timestamps
end
end
end
class UpdateEvalForms < ActiveRecord::Migration[6.1]
def change
change_table :eval_forms do |t|
t.belongs_to :author_user_details, foreign_key: { to_table: :user_details }
t.integer :year # consider using `YEAR(4)` instead
t.string :semester
t.boolean :is_archived, default: false
end
end
end
If you continue using a strange mix of camelCase and PascalCase you will need to explicitly configure all your associations and you will lose all the advantages of convention over configuration. I would not recommend this at all unless you're stuck with a legacy database as its a surefire recipe for developer confusion and bugs.
You will also get a missing constant error if you call the PascalCase methods without an explicit recipient (self):
class EvalForm < ApplicationRecord
def short_description
# uninitialized constant Description (NameError)
Description.truncate(27, separator: ' ')
end
end
While you can fix this with self.Description.truncate(27, separator: ' ') its still very smelly.
In this case if you want to call the column author_user_details_id instead of user_details_id which is derived from the name you need to configure the assocation to use the non-conventional name:
class EvalForm < ApplicationRecord
belongs_to :user_detail, foreign_key: :author_user_details_id
end
class UserDetail < ApplicationRecord
has_many :eval_forms, foreign_key: :author_user_details_id
end
If the rest of your schema looks like this you'll have to do this across the board.

How to build M to M relationships between two data model

Condition:
Tutor can follow or dis-follow student
Student can follow or dis-follow tutor
Tutor and Student are two different data models.
I build a middle data model calledapplication connecting both model
Application model:
class CreateApplications < ActiveRecord::Migration
def change
create_table :applications do |t|
t.belongs_to :tutor, index: true
t.belongs_to :student, index: true
t.timestamps null: false
end
end
end
application.rb
class Application < ActiveRecord::Base
belongs_to :tutor
belongs_to :student
end
student.rb
has_many :applications, :dependent => :destroy
has_many :tutors, through: :applications
tutor.rb
has_many :applications, :dependent => :destroy
has_many :students, through: :applications
schema.rb
ActiveRecord::Schema.define(version: 20170421093747) do
create_table "applications", force: :cascade do |t|
t.integer "tutor_id"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "applications", ["student_id"], name: "index_applications_on_student_id"
add_index "applications", ["tutor_id"], name: "index_applications_on_tutor_id".......
show.html.erb
# -->show who is the follower & person who followed & How many of them
# Error :undefined method `student' for nil:NilClass
<%= #application.student.count %> #undefined method `student' for nil:NilClass
<%= #application.tutor.count % >#undefined method `student' for nil:NilClass
<%= #application.student%> #undefined method `student' for nil:NilClass
In your controller, you need to set the #application variable.
def show
#application = Application.find(params[:id])
end
I would strongly advise against naming this class Application. The word "Application" has special meaning in Rails and you will almost certainly run into conflicts. Same goes for ApplicationController.
When model has many relationship with other model you want to use plural name, i.e. tutor.students or student.tutors
UPD: Also, you didn't pass variable into view properly, check your controller.
UPD1: Also, it's meaningless to use count on application record (btw you want to rename it to something like Courses, Classes or just StudentTutor because Application.rb is the model all your models inherit from by default, same goes to ApplicationController). It always has one student and one tutor (it belongs to both models). If you want to count related records you want to find student (if you want to count his tutors) or tutor (for vise-versa)

Rails searches the mistaken has_and_belongs_to_many table

I want to show all types which are related to a specific organisation in a select box of my document form. Types are part of the Ar engine. Organisations are part of another existing engine.
module Ar
module OrganisationPatch
extend ActiveSupport::Concern
included do
attr_accessible :ar_document_id
has_many :ar_documents, :class_name => 'Ar::Document'
has_and_belongs_to_many :ar_types, :class_name => 'Ar::Type'
end
end
end
module Ar
class Type < ActiveRecord::Base
attr_accessible :name
has_many :documents
has_and_belongs_to_many :organisations
end
end
class CreateTypeOrganisations < ActiveRecord::Migration
def change
create_table :ar_type_organisations, id: false do |t|
t.uuid :type_id, index: true
t.uuid :organisation_id, index: true
end
end
end
In my documents_controller I load types for forms about the before filter. The superior returns the organisation object:
def load_form_objects
unless current_user.admin?
#types = current_user.superior.ar_types
else
#types = Type.all
end
end
Calling the page I get this error and ask me why he is looking for a table called organisations_types:
ActiveRecord::StatementInvalid in Ar/documents#new
Mysql2::Error: Table 'portal.organisations_types' doesn't exist:
SELECT ar_types.* FROM ar_types INNER JOIN organisations_types
ON ar_types.id = organisations_types.type_id WHERE
organisations_types.organisation_id =
x'891c3986b33845d08d3951645a4f27d5'
Someone knows what I am doing wrong here?
Your table name isn’t map with lexical order what has_and_belongs_to_many expect. ( Expected order is organisations_types )
So you have to add :join_table option in both model's association. Like this,
has_and_belongs_to_many :ar_types, :class_name => 'Ar::Type', join_table: "ar_type_organisations"
has_and_belongs_to_many :organisations, join_table: "ar_type_organisations"
Reference

ActiveRecord Multi-association query

So I've got three models:
User
User Interest
Interest Tags
User Model
class User < ActiveRecord::Base
has_many :user_interests
end
InterestTag Model
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
UserInterest Model
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
I'd like to use ActiveRecord to include the name of the user's interests when loading their profile using the following query:
#user = User.find(current_user.id, :include => [{:user_interests => :interest_tags}])
Migrations for interest_tags + user_interests
create_table :interest_tags do |t|
t.string :name, :null => false, :size => 30
t.timestamp :created_at
end
create_table :user_interests do |t|
t.integer :user_id
t.integer :interest_tag_id
end
What am I doing wrong?
You have to add an has_many :through association on User model.
class User < ActiveRecord::Base
has_many :user_interests
has_many :interest_tags, :through => :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :interest_tag
belongs_to :user
end
class InterestTag < ActiveRecord::Base
has_many :user_interests, :dependent => :destroy
validates :name, :uniqueness => true
end
Now you can eager load the tags as follows:
User.find(current_user.id, :include => :interest_tags)
Note:
You might want to look at the acts_as_taggable_on gem for your requirement.
I assume you are building a tagging system, like in stackoverflow: every user kann have multiple tags that they are interested in. In that case the user_interests table is only a join table and does not need a model. Just use has_and_belong_to_many on the two real models.
See also this article on different ways to implement tagging with more or less normalized relational databases. You could also use a non-relational database like Mongodb, there you would need only one table to do tagging, see the cookbook.

Create a form for accessing relationships in RoR 3

This is kind of evolution of my previous question (although I changed a lot, including model names so figured I should better create another thread).. and the nature of question changed as well..
Currently I am struggling to create a form for the function
#dot2.link!(#dot)
The function works fine in console.
Here is the info from models / controllers (I tried to keep it minimal):
class User < ActiveRecord::Base
has_many :dots, :dependent => :destroy
....
end
Dot
class Dot < ActiveRecord::Base
belongs_to :user
has_many :linkages
...
def link!(new_dot)
linkages.create!(:end_id => new_dot.id)
end
...
end
Linkages:
class LinkagesController < ApplicationController
...
def create
#dot = current_user.dots.find(params[:linkages][:end_id])
#dot2 = Dot.find(params[:id])
#dot2.link!(#dot)
end
Linkages migration:
class CreateLinkages < ActiveRecord::Migration
def self.up
create_table :linkages do |t|
t.integer :start_id
t.integer :end_id
t.timestamps
end
add_index :linkages, :start_id
add_index :linkages, :end_id
add_index :linkages, [:start_id, :end_id], :unique => true
end
...
end
Now in console if I do
User.first.dots.first.link!(User.second.dots.second)
everything works fine.
How would I create a form for it (using just dot_id) as input??
Thanks!
Look up the collection_select form helper. It renders an association however you like, defaults to a Selectbox.