I am having trouble deciding which type of association fits best for my application. I have the following models: Firm, Client, and Case.
A Firm handles many Cases. And each Case can have one or more Clients assigned to it. Therefore my initial approach was:
class Firm < ActiveRecord::Base
has_many :cases
has_many :clients
end
class Case < ActiveRecord::Base
belongs_to :firm
has_many :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :cases
end
But I think something is not right with this. I was thinking a has_many :through Association would be better but I am not sure.
Rails Guides says that “You should use has_many :through if you need validations, callbacks, or extra attributes on the join model”. As the great coding philosopher Avi Flombaum once eluded, how can you possibly know that your join model will not serve an additional purpose this early in the application process. No matter where you are in the development stage, you can never see so far in the future to know you will not need to extend the join table.
Why You Don’t Need Has_and_belongs_to_many Relationships
With that out of the way:
# models/firm.rb
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
# models/client.rb
class Client < ActiveRecord::Base
belongs_to :firm
has_many :client_cases
has_many :cases, through: :client_cases
end
# models/case.rb
class Case < ActiveRecord::Base
has_many :client_cases
has_many :clients, through: :client_cases
end
# models/client_case.rb
class ClientCase < ActiveRecord::Base
belongs_to :client
belongs_to :case
end
UPDATE
Your issue must lie somewhere else; I was able to create the following in a terminal without any errors.
f = Firm.create name: 'Magical Group' # => #<Firm id: 1...
b = f.clients.create name: 'Billy' # => #<Client id: 1...
m = f.clients.create name: 'Megan' # => #<Client id: 2...
c = Case.create name: 'Billy & Megan are getting married!' # => #<Case id: 1...
b.cases << c # => #<ActiveRecord::Associations::CollectionProxy
m.cases << c # => #<ActiveRecord::Associations::CollectionProxy
f.cases.count # => 2
f.cases.uniq.count # => 1
I've provide the database schema, make sure yours resembles:
ActiveRecord::Schema.define(version: 20150813173900) do
create_table "cases", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "client_cases", force: :cascade do |t|
t.integer "client_id"
t.integer "case_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "client_cases", ["case_id"], name: "index_client_cases_on_case_id"
add_index "client_cases", ["client_id"], name: "index_client_cases_on_client_id"
create_table "clients", force: :cascade do |t|
t.string "name"
t.integer "firm_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "clients", ["firm_id"], name: "index_clients_on_firm_id"
create_table "firms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Assuming that a case will never belong to a different firm:
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_and_belongs_to_many :cases
end
class Case < ActiveRecord::Base
has_and_belongs_to_many :clients
end
Migrate join table
rails g migration CreateJoinTableCaseClient case client
Related
The case is that I have two models with relations many to many. But I want to make a query where I have the number of models multiplied by the relations where I have the same main model with one that has one relation. Here is the case in code:
# models/physician.rb
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
# models/patient.rb
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
# models/appointment.rb
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
# models/extended_physician.rb
class ExtendedPhysician < ApplicationRecord
self.primary_key = 'id'
has_one :appointment, foreign_key: 'physician_id'
has_one :patient, foreign_key: 'physician_id', through: :appointment
end
# sql view created in scenic for extended_physician model
SELECT "physicians".* FROM "physicians"
LEFT OUTER JOIN "appointments" ON "appointments"."physician_id" = "physicians"."id"
LEFT OUTER JOIN "patients" ON "patients"."id" = "appointments"."patient_id"
# schema
create_table "appointments", force: :cascade do |t|
t.bigint "physician_id"
t.bigint "patient_id"
t.datetime "appointment_date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["patient_id"], name: "index_appointments_on_patient_id"
t.index ["physician_id"], name: "index_appointments_on_physician_id"
end
create_table "patients", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "physicians", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_view "extended_physicians", sql_definition: <<-SQL
SELECT physicians.id,
physicians.name,
physicians.created_at,
physicians.updated_at
FROM ((physicians
LEFT JOIN appointments ON ((appointments.physician_id = physicians.id)))
LEFT JOIN patients ON ((patients.id = appointments.patient_id)));
SQL
# test
RSpec.describe ExtendedPhysician, type: :model do
describe 'patiens relation' do
it 'retunrs correct patiens' do
patient1 = Patient.create!
patient2 = Patient.create!
Physician.create!(patients: [patient1, patient2])
expect(ExtendedPhysician.count).to eq(2)
# this not pass because relations ids are same like [patien1.id, patient1.id]
expect(ExtendedPhysician.all.map(&:patient).pluck(:id)).to eq([patient1.id, patient2.id])
end
end
end
And as you can see this test will not pass.
I would like to achieve exactly that this test passes it does not have to be a has_one relation it may be some method in the model but I do not know how to do it right
I'm writing a simple blog-style Rails app and have successfully added a rather simple #tags function. The problem I'm facing is that I cant think of a way to remove tags that are no longer used in any post. For example, if I make a tag #twitter in the first post, then remove the said post, tag #twitter will remain in my database even though it's not used/referenced anywhere. I would like to remove unused tags. Third DB table is created for relation (post has many tags, tags belong to many posts) and the relation between tag and post is "has_and_belongs_to_many"
**tag.rb**
class Tag < ApplicationRecord
has_and_belongs_to_many :posts
end
**post.rb**
belongs_to :user
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
has_and_belongs_to_many :tags, dependent: :destroy
after_create do
hashtags = self.body.scan(/#\w+/)
hashtags.uniq.map do |hashtag|
tag = Tag.find_or_create_by(name: hashtag.downcase.delete('#'))
self.tags << tag
end
end
before_update do
self.tags.clear
hashtags = self.body.scan (/#\w+/)
hashtags.uniq.map do |hashtag|
tag = Tag.find_or_create_by(name: hashtag.downcase.delete('#'))
self.tags << tag
end
end
from **post_controller.rb**
def hashtags
tag = Tag.find_by(name: params[:name])
#posts = tag.posts.page(params[:page])
end
**schema.rb**
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.string "image_filename"
t.string "posts"
t.string "image"
end
create_table "posts_tags", id: false, force: :cascade do |t|
t.bigint "post_id"
t.bigint "tag_id"
t.index ["post_id"], name: "index_posts_tags_on_post_id"
t.index ["tag_id"], name: "index_posts_tags_on_tag_id"
end
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
One way I created tagging was to create a Tag model and table, and then a Taggings model and table.
tag.rb
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings
end
tagging.rb
class Tagging < ApplicationRecord
belongs_to :tag
belongs_to :post
end
post.rb
class Post < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
end
So now you just have the join table that uses a post_id and tag_id to join the two. This way you can enforce uniq on your tag table to avoid redundant tags. As mentioned above you could build an after_destroy method that deletes a tag after checking to see if it is used anywhere else. Or like Dave Newton mentioned keep it around for historic purposes for autocomplete suggestions.
I think I have missed something when creating an association with a foreign_key in Rails.
I have 2 models: Company and Employee. Company has_many employees and Employee belongs_to a company. Company has an attribute called company_code and I should be able to figure out which company the employee works for using the company_code instead of company_id.
At first, I created the models:
rails g model Company company_code:integer:index name
rails g model Employee joined:date:index salary:integer
Then, I generated a migration to add the company_code column to the employees table.
class AddReferenceToEmployee < ActiveRecord::Migration[5.1]
def change
add_column :employees, :company_code, :integer, index: true
add_foreign_key :employees, :companies, column: :company_code
end
end
And, finally I added the foreign key at the model level.
class Company < ApplicationRecord
has_many :employees, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code
end
However, I'm still not able to create proper association.
company = Company.create(name: 'test', company_code: 123)
company.employees.create(joined: Date.today, salary: 1000)
It creates employee record with company_code = 1 instead of 123.
When I try to create a new instance of employee
company.employees.new
It will generate
#<Employee id: nil, joined: nil, salary: nil, created_at: nil, updated_at: nil, company_code: 1>
What am I missing? Is this the right way to do it?
Bellow is my schema.rb
ActiveRecord::Schema.define(version: 20180828052633) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "companies", force: :cascade do |t|
t.integer "company_code"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_code"], name: "index_company_on_company_code"
end
create_table "employees", force: :cascade do |t|
t.date "joined"
t.integer "salary"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "company_code"
end
add_foreign_key "employees", "companies", column: "company_code"
end
class Company < ApplicationRecord
has_many :employees, primary_key: :company_code, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code, primary_key: :company_code
end
This is my Schema.rb
Schema.rb
ActiveRecord::Schema.define(version: 20170617073406) do
create_table "actions", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_id"
t.datetime "timestamp"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "user_id"
t.datetime "last_activity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
These are my three model classes.
Action.rb
class Action < ApplicationRecord
belongs_to :post
has_many :users, :foreign_key => 'user_user_id'
end
Post.rb
class Post < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
User.rb
class User < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
I am trying to add an instance of Action object into Post model object.
post = Post.find(post_id=post_id)
current_action = post.actions.find_or_create_by(name: "like")
It gives me the following error:
SQLite3::SQLException: no such column: actions.action_id: SELECT "actions".* FROM "actions" WHERE "actions"."action_id" = ? AND "actions"."name" = ? LIMIT ?
I am new to Ruby on Rails and come from Django background. Please help me figure this out.
Action needs to be related to the user as many to many...
If you want a many-to-many association, you will need to use another table (i.e. a join model):
class Action < ApplicationRecord
belongs_to :post
has_many :user_actions
has_many :users, through: :user_actions
end
class User < ApplicationRecord
has_many :user_actions
has_many :actions, through: :user_actions
end
class UserAction < ApplicationRecord
belongs_to :action
belongs_to :user
end
The ID I want to store is longer than Integer can do.
You can specify your own id in the migration and avoid adding an extra action_id:
class CreateActions < ActiveRecord::Migration[5.1]
def change
create_table :actions, id: false do |t|
t.string :id, null: false
t.timestamps
end
add_index :actions, :id , unique: true
end
end
With the above setup, you don't need to specify any foreign_key in Post either, ActiveRecord will use defaults (i.e. action_id):
class Post < ApplicationRecord
has_many :actions
end
A note about associations and foreign keys (and why you got that error):
Whenever you create an association, the foreign_key must be created in the table with the belongs_to side, since ActiveRecord will look for that key there.
Even If you don't specify a belongs_to, a has_many reference to that table will still look for that foreign_key.
So, when you add
has_many :actions, :foreign_key => 'action_id'
you are telling ActiveRecord to look for action_id column in actions table, but that columns has not being created in actions table.
In the proposed solution, the foreign keys are on the join table model (i.e. UserActions), so you must create a migration to include them:
rails g migration CreateUserActions user:references action:references
Solution:
Run migrations in command line:
rails generate migration add_post_to_actions post:belongs_to
rake db:migrate
Then update:
class Action < ApplicationRecord
belongs_to :post
# ...
end
class Post < ApplicationRecord
self.primary_key = 'post_id'
has_many :actions
# ...
end
class User < ApplicationRecord
self.primary_key = 'user_id'
# ...
end
Explanation:
1st line would add post_id column to actions table, and then index it with foreign constraint
The above migrations are independent of the contents of your current model files. You can even delete your models/action.rb or models/user.rb, and you'll see that the migrations would still even run without problems, because migrations only "do" stuff on the actual current database. The migration files also do not even care about whatever is written in your schema.rb, although it will update that schema.rb each time you run a migration (after the database has already been migrated/updated).
I have created a blank rails app (rails new cheese_shop), with two models and a join table. I am trying to create a cheese shop and specifying which cheeses it contains, at creation time, like this:
cheeses = [
Cheese.create!(name: "Bree"),
Cheese.create!(name: "Kačkavalj"),
]
Shop.create! name: "The Whistling Cheese", cheeses: cheeses
However, I'm getting this error:
SQLite3::ConstraintException: NOT NULL constraint failed: stocks.shop_id: INSERT INTO "stocks" ("cheese_id", "created_at", "updated_at") VALUES (?, ?, ?)
Apparently, the shop ID is not inserted to the stocks table when I create the shop. Is it possible to fix this, without having to do it in two steps (i.e. without first creating the Shop, and then adding the cheeses?)
Here are my models:
class Cheese < ActiveRecord::Base
has_many :shops, through: :stocks
has_many :stocks
end
class Shop < ActiveRecord::Base
has_many :cheeses, through: :stocks
has_many :stocks
end
class Stock < ActiveRecord::Base
belongs_to :shop
belongs_to :cheese
end
My migrations look like this:
class CreateTables < ActiveRecord::Migration
def change
create_table :cheeses do |t|
t.string :name, null: false
t.timestamps null: false
end
create_table :shops do |t|
t.string :name, null: false
t.timestamps null: false
end
create_table :stocks do |t|
t.integer :shop_id, null: false
t.integer :cheese_id, null: false
t.integer :amount
t.float :price
end
end
end
maybe you should try to use nested attributes:
class Shop < ActiveRecord::Base
has_many :cheeses, through: :stocks
has_many :stocks
accepts_nested_attributes_for :stocks
end
and then you will be able to do something like:
cheese = Cheese.create!(name: "Bree")
params = { attrib: { name: "The Whistling Cheese", stocks_attributes: { cheese_id: cheese.id} } }
Shop.create params[:attrib]
here is doc: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It turns out Rails creates the associations in two steps, first leaving out the Shop ID, then setting the Shop IDs with an UPDATE, all in one transaction. So The NOT NULL constraints are causing the problem.
Changing this:
t.integer :shop_id, null: false
t.integer :cheese_id, null: false
…to this:
t.integer :shop_id
t.integer :cheese_id, null: false
…solves the problem, although I'm unhappy with this since now I cannot rely on the database to ensure the integrity of my data.