Rails app:
A user has_many positions.
Each position has one company (company name and company id) per the following schema:
create_table "positions", :force => true do |t|
t.integer "user_id"
...
t.string "company"
t.integer "company_id"
end
I would like users to be able to "follow" as many individual companies as they would like (i.e. a user can follow many different companies a company can be followed by many different users). It would seem that this calls for a has_and_belongs_to_many relationship between users and positions, but I want users to be able to follow the company attribute of a position row and not the position itself.
Should I create a new "following" table altogether that would pull companies from the positions table to be matched to user_id's? Or is there a way I can set up a has_many :through relationship and map user_id's to company_id's?
Thank you!
What I think you could have
A User table:
integer User_Id
....
A Company Table:
string company
integer company_id
...
A Positions table:
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
A Following table (If the user can follow any comapny regarding of whether he has a position in it):
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
OR if the user can only follow a company that he has position in then you can add a new column to position table. This would be a boolean flag telling if the user if 'following' the company identified by the position. Alternatively the Following table can also map user to position in this case.
I broadly agree with MickJ, although having created the Company and User models/tables (which obviously have an id column in each) I'd do it as:
create_table "companies" do |t|
t.string "name"
...
end
create_table "positions" do |t|
t.references "user"
t.references "company"
...
end
create_table "followings" do |t|
t.references "user"
t.references "company"
...
end
Models:
class User
has_many :positions
has_many :followings
end
class Company
has_many :positions
has_many :followings
end
class Position
belongs_to :user
belongs_to :company
end
class Following
belongs_to :user
belongs_to :company
end
You could reference the company from the position by doing:
position = Position.first
puts position.company.name
or by user with something like
user = User.first
user.positions.each do |position|
puts position.company.name
end
-- EDIT1:
To extract the company name from positions into a separate table you'd be best off writing a little rake task - something like:
Position.all.each do |position|
company = Company.find_or_initialize_by_name(position.company_name)
position.company_id = company.id
position.save
end
Then you might want to write a migration to remove the company name column from the positions table ... just to keep things tidy.
Related
I was reading another question on here regarding referencing columns from two separate tables but was a little confused if it addressed my issue. What's going on is I have two tables, Destination and Booking. The Destination table has a column for location_id, and the Booking has a column for location, and I am trying to reference location in Booking table from location_id column in Destination table.
Here is my table for Booking(migration)
class CreateBookings < ActiveRecord::Migration[6.1]
def change
create_table :bookings do |t|
t.string :name
t.string :start_date
t.string :end_date
t.string :email
t.integer :location
t.timestamps
end
end
end
and here is my table(Migration) for Destination
class CreateDestinations < ActiveRecord::Migration[6.1]
def change
create_table :destinations do |t|
t.string :address
t.string :city
t.string :state
t.string :zip
t.integer :location_id
t.timestamps
end
end
end
My Models are setup currently as
class Booking < ApplicationRecord
# belongs_to :reservation, optional: true
has_many :destinations, :class_name => 'Destination', :foreign_key=> 'location_id'
validates :name, :start_date, :end_date, :email, presence: true
end
and
class Destination < ApplicationRecord
has_many :bookings, :class_name => 'Booking', :foreign_key=> 'location'
end
Am I currently referencing the columns correctly, or is there something else I should be doing?
How you should write your migrations depends on the association between your models. Foreign keys go onto tables that have a belongs_to association.
Can a single Booking have multiple Destinations? If the answer is no, you need to change the association in your Booking model to belongs_to :destination and then put a :destination_id on your bookings table (you can give it a custom name like :location_id if you want but the convention is to use the model name).
If a single Booking can have multiple Destinations, and surely a single Destination can have multiple Bookings, then you have a many-to-many relationship. In that case you will not put foreign keys on the destinations table, nor the bookings table. Instead you will need a join table between them and that's where the foreign keys go.
Rails gives 2 different ways to declare many-to-many relationships. See https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many.
If you want to use has_and_belongs_to_many, your models would look like this:
class Booking < ApplicationRecord
has_and_belongs_to_many :destinations
end
class Destination < ApplicationRecord
has_and_belongs_to_many :bookings
end
And the migration would look like this:
class CreateBookingsAndDestinations < ActiveRecord::Migration[6.0]
def change
create_table :bookings do |t|
# ...
end
create_table :destinations do |t|
# ...
end
create_table :bookings_destinations, id: false do |t|
t.belongs_to :booking
t.belongs_to :destination
end
end
end
Caveat: Based on your question I'm assuming you want a booking to have a destination. If you want a destination to many bookings and vise-versa, Sean's answer is great.
I think you're misunderstanding how foreign keys / associations work in databases.
It sounds like you want a column in the bookings table to "reference" a value column in the destinations table (or maybe the opposite), as in:
bookings.location -> destinations.location_id or maybe destinations.location_id -> bookings.location.
That's not typically what we mean by "reference" in a relational database. Instead, when you say that a table (for example, a 'comments' table) references another table (for example, a comments table references a user table), what we typically mean is that we're storing the primary key column of the referenced table (e.g. the user's id) in a column in the first table (e.g. comments.user_id --> users.id).
From an english language standpoint I expect that you want a booking to refer to a destination, so I'm going to assuming we want a the booking table to reference/refer to the destinations table, like this:
booking.location -> destinations.id
In Ruby on Rails, the convention is to name a column that stores an association with the same as the table it references, plus _id, like so the convention would be this:
booking.destination_id -> destinations.id
A common way to create this in a migration would be with:
add_reference :bookings, :destination
When adding a reference in a database you almost always want to index by that value (so that you can do Bookings.where(destination_id: #destination.id) and not kill your database). I am also a strong advocate for letting your database enforce referential integrity for you, so (if your database supports it) i'd recommend the following:
add_reference :destinations, :booking, index: true, foreign_key: true
This would prevent someone from deleting a destination that has a booking associated with it.
I have a table called customers. These table has two addresses . One address of work and One direccion of house
Those 2 addresses belong to a table called addresses
I don't know how to relation those 2 tables
Migrations
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
t.integer :address_id #Address of work
t.integer :address_id_1 #Address of home
t.timestamps
end
end
end
class CreateAdresses < ActiveRecord::Migration
def change
create_table :adresses do |t|
t.string :street
t.timestamps
end
end
end
I do not believe this is a good approach or database design. If you want to proceed this way and not get out of the rails convention just create two columns address_id and address_two_id
and in customer.rb
belongs_to :address, class_name: "Address"
belongs_to :address_two, class_name: "Address"
By default rails takes the name of the foreign key and stores it in a column called "name"+"_id"
The better way is two have a column customer_id in your Address model and create a relation in your customer class
customer.rb
has_many :addresses
And you can also validate that a customer has no more than two addresses by adding this validation to
address.rb
validate :validate_two_addresses
def validate_two_addresses
address_count = Address.where(customer_id: self.customer_id).count
errors.add(:base, "You cannot have more than 2 addresses.") unless address_count < 3
end
I am in the process of building a multiclient system in ROR. (I am looking at http://guides.rubyonrails.org/association_basics.html#polymorphic-associations)
The structure is that a client has a contract, so when he logs in with his username, password and contract, he will have access to the system.
We have the contract id as a “master key”, which has to be in every table in the system.
class CreateContracts < ActiveRecord::Migration
def change
create_table :contracts do |t|
t.integer :contract_id
end
end
end
(chart of accounts)
class CreateCoas < ActiveRecord::Migration
def change
create_table :coas do |t|
t.integer :account_id
t.string :account_name
end
end
end
class CreateCustGroups < ActiveRecord::Migration
def change
create_table :custgroups do |t|
t.integer :account_id1
t.integer :account_id2
t.integer :account_id3
end
end
end
Q1: How do I define the contract with belongs_to? There has to be a relation in every table in the system to the contract table. Do I have to have a relation to all tables? (I think so)
class Contracts < ActiveRecord::Base
has_and_belongs_to_many :Coas
has_many:xxx
belongs:to
end
Q2: How do I define the association on the custgroup? Here we have a record where I have 3 or more fields that link to the same table (COA).
As Jesper said, it's quite hard to follow what you're trying to achieve, but I'll try to reply to your questions :
Q1 : If you want all your tables to reference a contract, you'll need to add to all those tables a foreign_key such as contract_id
so each create_table call will have the contract_id key
create_table :new_models do |t|
t.belongs_to :contract # this will create a contract_id field
end
you can also add an index on the column
add_index :new_models, :contract_id
then in all you models you'll add the belongs_to association :
class NewModel
...
belongs_to :contract
...
end
so if your Coas & CustGroups needs to reference the contract table, you'll have to change both migrations to include the contract_id key and then the models to add the belongs_to association
If a contract needs to have access to all Coas that references it, then you need to use the has_many association
class Contracts < ActiveRecord::Base
...
has_many :coas
...
end
It doesn't look like you need a has_and_belongs_to_many here, but i might be wrong about that.
if a contract also needs to access to CustGroups, you'll add :
has_many :cust_groups in the Contract model.
Q2 : I really didn't get understand what you want to do. Please explain what is the relation between Coas and Custgroups and I'll try to help you
I have tables Users, Locations and Follows, where Users have_many locations :through => :follows. Follows belongs_to both users and locations. I want to add a row into the Follows table--that is, create a Following relationship between a user and a location.
I'm not sure how to do this, or if I'm implementing Follows right:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows |do| t
t.references :user_id
t.references :location_id
t.timestamps
end
end
end
And the code I'm trying to use to add a Follows relationship, given userid and locationid, is
newFollow = Follow.new(:user_id => userid, :location_id => locationid)
newFollow.save
I'm getting the error unknown attribute: user_id.
any ideas? I'm really stuck. Thanks so much!
In a migration, references expects the field name without the _id, and it then appends _id to it. Right now, you're creating two columns: user_id_id and location_id_id.
Instead of these lines...
t.references :user_id
t.references :location_id
... you need these lines:
t.references :user
t.references :location
And once you've fixed your column names...
You don't have to manually create records in then "through" table. If you have a user, and you have a location, and your associations are setup correctly (has_many follows; has_many :locations, through: :follows), you can simply use
user.locations << location
This will automatically create the joining record.
I have column LTD in my Company model. After retrieving value from model using
Company.find
If any value is present in LTD column, then I have to display the text "Limited" on the view. I have many columns in the model which are in the abbreviated form and when value is present their long form are displayed on the view. Therefore writing conditions on the view is not feasible.
I was thinking whether writing a custom rails config file containing application constants will do. But I don't have quantitative and qualitative information on this.
Please help. Thanks in advance.
You could create a separate Abbreviation model that your Company model could be associated with through a join model CompanyAbbreviation. Then there would be one join table record for each column in a specific company record. Rather than having each abbreviation as a column in your companies table you would have secondary keys in your company_abbreviations table referring to the associated company and abbreviation records.
Something like the following:
class Company < ActiveRecord::Base
has_many :company_abbreviations
has_many :abbreviations, :through => :company_abbreviations
end
class Abbreviation < ActiveRecord::Base
has_many :company_abbreviations
end
class CompanyAbbreviation < ActiveRecord::Base
belongs_to :company
belongs_to :abbreviation
end
class CreateAbbreviations < ActiveRecord::Migration
def self.up
create_table :abbreviations do |t|
t.string :abbr
t.string :description
end
add_index :abbreviations, :abbr
end
end
class CreateCompanyAbbreviations < ActiveRecord::Migration
def self.up
create_table :company_abbreviations do |t|
t.references :company
t.references :abbreviation
end
add_index :company_abbreviations, :company_id
add_index :company_abbreviations, :abbreviation_id
end
end
In db/seeds.db you could pre-populate your abbreviations table.
You add new associations like this:
#company.company_abbreviations.create(:abbreviation => Abbreviation.find_by_abbr("LTD"))
In your view you can reference the expanded abbreviation columns cleanly like this:
<% #company.abbreviations.each do |abbr| %>
<%= abbr.description %>
<% end %>
You may also want to control the display order in some fashion, say by a sort column in the join table,
This works for me perfectly.
I have declared a global hash in config/environment.rb which maintains the list of all the column name short-forms and long-forms and on the view I just check if value is present in the column I search for the corresponding key value pair from the global hash and display the long-form.
Thanks guyz for giving your time to help me.