Rails Postgres many to many relationship with custom foreign key - ruby-on-rails

I have a fact table, clients with a bunch of businesses:
bus_id, sales, date
1, $986, 1/1/2016
1, $543, 1/2/2016
2, $921, 1/1/2016
2, $345, 1/2/2016
I want to create a table opportunities
bus_id, opportunity
1, "Upsell"
1, "Upsell More"
How do I create the opportunities table so that I could display the opportunities per bus_id?

Here is the migration command:
bin/rails g model opportunity custom_foreign_bus_id:integer:index description
businesses.rb
has_many :opportunities, foreign_key: :custom_foreign_bus_id
opportunity.rb
belongs_to :business, foreign_key: :custom_foreign_bus_id
Then to get the Business Opportunities:
Business.find(1).opportunities
or simply:
Opportunity.where(custom_foreign_bus_id: 1)
To do a many-to-many you need a has_and_belongs_to_many association or a join model, is that what you really want?
2.8 Choosing Between has_many :through and has_and_belongs_to_many

Related

Creating link table to connect one to one relationship of same model

I want to make sure table join with joining table has strong one-to-one relationship. Following is example
Run generator
bundle exec rails g model Page name:string content:text
bundle exec rails g model PageAssociation left_page_id:integer right_page_id:integer
After running these generator, I have to change my page model following
class Page < ApplicationRecord
has_one :left_page_association, :foreign_key => :left_page_id,
:class_name => 'PageAssociation'
has_one :right_page_association, :foreign_key => :right_page_id,
:class_name => 'PageAssociation'
has_one :left_page, :through => :left_page_association,
:source => :right_page
has_one :right_page, :through => :right_page_association,
:source => :left_page
end
In my association table model page_association
class PageAssociation < ApplicationRecord
belongs_to :left_page, class_name: 'Page'
belongs_to :right_page, class_name: 'Page'
end
Now using rails c I can do following
page_one = Page.create(name: 'first page', content: 'first page content')
page_two = Page.create(name: 'second page', content: 'second page content')
PageAssociation.create(left_page: page_one, right_page: page_two)
page_one.left_page
page_two.right_page
All working fine and it is returning pages. but I can do also this
page_three = Page.create(name: 'third page', content: 'third page content')
PageAssociation.create(left_page: page_one, right_page: page_three)
Of course it still shows on relationship due to has_one but it is creating another relationship. So I want following
Only one association can be created and it should not allow another one
What is best way to do this as we have million of records
Is it one to use page.left_page? or is there any other optimised way to do same thing.
Should I add indexing using following migration lines. Does it effect performance.
add_foreign_key :page_associations, :pages, column: :left_page_id
add_foreign_key :page_associations, :pages, column: :right_page_id
I changed migration to make column value unique, so it now give error when I create another PageAssociate with same page.id but is it right way?
t.integer :left_page_id, null: false, index: { unique: true }
t.integer :right_page_id, null: false, index: { unique: true }
what problem I am solving
So I have books table where I have multiple books, but some books borrow pages from one another. So suppose I have book-A and book-B so I want to show relationship of book-a to book-b. If they are related then I will create another relationship of book-a page-10 linked to book-b page 20, so when I click on book-a to synchronise, it will bring all changes of book-b page 20 in my book-a.
So above is my first step to connected two books. I understand solving it using self join and keys is best but I can't do as we have huge number of records so that is not possible. So I have used above method to do it. But I did add unique constraints on db and validations in model to solve it. but I feel it is still not good way of doing it.
later one I will make following
main-book , main-book-page, secondary-book-page
This will allow me to bound later pages each other. The name book and pages are fictions, actually entities are different.
What you want here is really just a normal many to many table setup:
class Book < ApplicationRecord
has_many :book_pages
has_many :pages, through: :book_pages
end
class Page < ApplicationRecord
has_many :book_pages
has_many :books, through: :book_pages
end
class BookPage < ApplicationRecord
belongs_to :book
belongs_to :page
validates_uniqueness_of :book_id, scope: :page_id
end
Uniqueness in this case can be guarenteed by adding a unique index:
add_index :book_pages, [:book_id, :page_id]
Why?
M2M join table setups where you have two foreign keys that are assigned arbitrarily are not a very good design and don't work well with ActiveRecord since you can't define assocations with an OR clause.
Each association can only have a single foreign key. This means that you can't treat it as a homogenius collection and cannot eager load it.
That means you need to write crappy joins like this instead of being able to work with a proper assocation:
Book.joins(
"LEFT JOIN pages_assocations pa ON pa.left_page_id = books.id OR pa.left_page_id = books.id"
)
And you also have to write steaming piles when creating indirect assocations.
While the table setup with a single row per book/page combo may seem to require more rows on the onset its also much more flexible as you can map out the assocations between books by subqueries, lateral joins or grouping and counting the number of matches.
class Book < ApplicationRecord
has_many :book_pages
has_many :pages, through: :book_pages
def books_with_pages_in_common
Book.where(
id: BookPage.select(:book_id)
.where(page_id: pages)
)
end
end

Rails validation on a joint table

I have following models:
Product (id, name):
has_many :prices
Product_price (id, product_id, price): The thing is that each product can have different prices
belongs_to :product
Subscription (id, name):
has_many :subscription_price_sets,
foreign_key: :subscription_price_set_id,
inverse_of: :subscription
has_many :product_prices, through: :subscription_price_sets
Subscription_price_set (id, product_price_id, subscription_id):
belongs_to :subscription,
foreign_key: :subscription_id
belongs_to :product_price,
foreign_key: :product_price_id
How do I validate it, so that for a given subscription it's impossible to have a product with two different prices?
For example:
I have two products: Notebook (id: 1) and Pencil (id: 2)
And their prices are:
Product_prices:
(id: 1, product_id: 1, price: 4)
(id: 2, product_id: 1, price: 12)
(id: 3, product_id: 1, price: 10)
(id: 4, product_id: 2, price: 3)
(id: 5, product_id: 2, price: 2)
And a Basic subscription:
(id: 1, name: "Basic")
Let's say I have Subscription_price_set:
(id: 1, product_price_id: 1, subscription_id: 1)
Now I should be able to create another Subscription_price_set with subscription_id: 1, but the only allowable product_price_ids should be id: 4 and id: 5.
Any hints on how to achieve that?
Use scope to make a uniqueness validation on multiple columns:
validates_uniqueness_of :subscription_id, scope: :product_price_id
However this does not actually guarantee uniqueness.
To safeguard against race conditions you need to compliment the validation with a database index:
class AddIndexToSubscriptionPriceSets < ActiveRecord::Migration[6.0]
def change
add_index :subscription_price_sets, [:subscription_id, :product_price_id] , unique: true
end
end
Your also using the foreign_key option all wrong. Rails is driven by convention over configuration and will derive the foreign key from the name of the association. You only ever need to specify foreign_key if the name of the association does not match.
belongs_to :subscription
belongs_to :product_price
On the has_many association it will actually cause an error:
has_many :subscription_price_sets,
foreign_key: :subscription_price_set_id,
inverse_of: :subscription
This will result in the following join
JOINS subscription_price_sets ON subscription_price_sets.subscription_price_set_id = subscriptions.id
Which of course will blow up as there is no such column. The foreign_key option on a has_many association is used to specify which column on the other table that corresponds to this table. All you really need is:
has_many :subscription_price_sets
Rails can also deduce the inverse of an association based and you only need to specify when you are "going off the rails" and the names don't match up.
I've created a custom validation method in Subscription_price_set model, and it did the trick :)
validate :product_uniqness
private
def product_uniqness
return unless subscription.product_prices.pluck(:product_id)
.include?(product_price.product_id)
errors.add(:product_price_id, 'You can\'t add the same product twice')
end

Ruby on Rails having more than 1 different column foreign keyed to same table

I am very new to Ruby on Rails but I am decent with SQL. I am currently looking at trying to make an association for two tables... well actually more but this one example should answer my question for all. These are fictional but represent my issue so don't mind if they wouldn't follow best practices...
Tables: USER, LOCA --- They had once limited table name characters back in the day.
-LOCA
LOCATIONID - PK
-USER
USERID - PK; LIVES_LOCATION - FK (LOCATIONID); WORKS_LOCATION - FK (LOCATIONID); MANAGERID....... etc
How do in Ruby on Rails, make the class with belongs_to and has_many with the correct foreign_key names to the correct columns?
class User
belongs_to :lives, class_name: 'Loca', foreign_key: 'lives_location'
belongs_to :works, class_name: 'Loca', foreign_key: 'works_location'
...
end
class Loca
has_many :users
...
end

single table inheritance subclass doesn't exist

I'm trying to play around with association tables as well as single table inheritance ('STI'). I have a Man, Woman, and a Relationship model. The Relationship model has a type column, so that I can use STI. I also created a Friend model that inherits from Relationship, as Friend will be one type of relationship.
Man.rb
attr_accessible :name
has_many :relationships
has_many :women, :through => :relationships
Woman.rb
attr_accessible :name
has_many :relationships
has_many :men, :through => :relationships
In the meetup model, I also wish to keep track of when and where the date took place.
Relationship.rb
attr_accessible :type
belongs_to :woman
belongs_to :man
Friend.rb
class Friend < Relationship
end
However, when I try to create a relationship with type friend, I get the warning message that the subclass doesn't exist. In the console, I'm doing this
sarah = Woman.create!(name: 'Sarah')
jim = Man.create!(name: 'Jim')
jim.relationships.build(type: 'Friend', woman_id: 1)
=> #<Relationship id: nil, type: "Friend", man_id: 1, woman_id: 1, created_at: nil, updated_at: nil>
jim.save!
but then when I try to pull up the relationships for jim, I get
>> jim.relationships
Relationship Load (0.3ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."man_id" = 1
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'friends'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Relationship.inheritance_column to use another column for that information.
The same is true when I try to create the relationship with 'friends' instead of 'Friend'
jim.relationships.build(type: 'friends', woman_id: 1)
can you explain what I'm doing wrong?
I didn't create a table in the db for friends. I thought everything could be stored in the Relationship model. Since this is just a play-around app, I only assigned the type attribute for the Relationship model.
i think the type value needs to be be lower case and singular - friend ?
but really you probably want to use the objects and let rails handle that for you
sarah = Woman.create!(name: 'Sarah')
jim = Man.create!(name: 'Jim')
Friend.create!(women: sarah, man: jim)
the Man Women models don't make sense to me - they are both a Person and a relationship should just have 2 people, regardless of gender

Creating controllers and views for a has_many :through relationship in Rails 3

There are many tutorials that show you how to create the model instructions for a has_many :through relationship in Rails, but there doesn't seem to be many articles related to the process of setting up forms to create and edit these relationships. I am seeking some assistance (or good examples) of how to create an interface that will allow users to manage these types of relationships in a Rails app.
Here's the scenario:
I have Users, Relationships, and Athletes. A User can have a
Relationship with an Athlete in a variety of roles: Coach, Mentor,
Parent, or Fan.
Here are my models:
class User < ActiveRecord::Base
has_many :relationships
has_many :athletes, :through => :relationships
end
class Athlete < ActiveRecord :: Base
has_many :relationships
has_many :users, :through => :relationships
end
class Relationship < ActiveRecord :: Base
belongs_to :users
belongs_to :athletes
end
So, the next step is to build the views and controllers that allows me to create a User-to-Athlete relationship (with a coach, parent, etc role), edit the relationship, or destroy the relationship.
Ultimately, my aim is to have a scenario where Users can create Athletes and choose the associated Relationship.
Unfortunately, I can't find any specific tutorials or references that gives me much more than the model instructions or the example for a has_many relationship.
If anyone has a link or example that can solve this problem at a simple level, I should be able to customize the rest.
The relationship you have here between your User and Athlete model is essentially a has_and_belongs_to_many relationship (HABTM). By going back and forth with you it seems you're confused about what the best way to create these relationships would be.
A good place to start reading would be in the documentation for ActiveRecord's Associations, specifically the documentation for HABTM relationships.
You're model setup is fine. Now that you have your HABTM relationship setup, here's what you can do. Let's assume both your Athlete and User model are very simple and have nothing but a name attribute, which is a string. You can now do code like this (this is console output from the rails console):
User.create(:name => "Jeff")
usr = User.first
=> #<User id: 1, name: "Jeff">
usr.athletes
=> []
atl = usr.athletes.create(:name => "Mike")
=> #<Athlete id: 1, name: "Mike">
The line above will create a user with name Mike, and automatically create a relationship entry with the appropriate attributes to link the two. So now if you call this:
usr.athletes
=> [#<Athlete id: 1, name: "Mike">]
Now if you wanted to allow the user to dictate what the relationship is between themselves and an Athlete upon Athlete creation, you could setup your Relationship class to have a relation field of type string, and when creating the relationships (as I just displayed above), you can then do something like this:
rel = usr.relationships.where(:user_id => usr.id, :athlete_id => atl.id).first
=> #<Relationship id: 1, user_id: 1, athlete_id: 2, :relation => nil>
rel.relation = "Friend"
rel.save
=> #<Relationship id: 1, user_id: 1, athlete_id: 2, :relation => "Friend">
Hopefully this is more helpful than my original answer. Let me know if you have any questions. And definitely be sure to look at the ActiveRecord Associations documentation I mentioned above.
Try railscasts or ascii casts. That's where I usually start. Not sure if this is what you're after but there's a tutorial on those sites for nested forms. I think it's under complex forms. Would e worth reading / watching anyway.

Resources