I am currently building my first application with Ruby on Rails (version 5.2.4.2 with ruby 2.6.3) and am having an issue with one of the associations. In my model there are applications in which you supply a single company that you are applying to. Logically I would like to be able to get an application and say application.company = Company.find_by(...).
To make this work, I have
# app/models/Application.rb
class Application < ApplicationRecord
has_one: company
end
# app/models/Company.rb
class Company < ApplicationRecord
end
# database migration
Class AddCompanyToApplication
def change
add_reference :companies, :application, foreign_key: true
end
end
Doing this allows me to use the desired syntax but has one issue. When I create a second application for the same company, the row in the companies table is changed that removes the company from the first application.
The list of companies is predetermined and I just want to refer to a company from within the application. Is there a way to be able to use the assignment syntax while having the foreign key placed in application rather than companies? From a database perspective I feel like application should hold the foreign key, but this breaks the assignment syntax. It seems like Ruby wants me to make Company have has_many :applications, but this breaks the semantics of what I am trying to accomplish. Is there a way to change the foreign key placement or should I forgo the assignment syntax and stick to SQL and assigning directly to id's?
Right now, you have a 1 to 1 relationship. An application has_one :company and a company belongs_to :application. That's why when you try to create a second application for the same company, the application_id in the companies table gets overridden. If you change your company association to has_many :applications, you should be able to add more than one application to a company. Then you can call application.company to return the application's current company or application.company = ... to assign a company to the current application.
class Application < ApplicationRecord
has_one :company
end
class Company < ApplicationRecord
has_many :applications
end
To make this work, you have to create a new migration:
Class AddCompanyIdToApplication
def change
add_reference :application, :company, foreign_key: true
end
end
Before you do that, you have to rollback your existing AddCompanyToApplication migration (which is named wrong, by the way, because you're not adding the company to the application, you're adding the application to the company) and delete the AddCompanyToApplication migration.
Read more about associations
Read more about add_reference
Related
In designing a data model, you will sometimes find a model that should have a relation to itself. Ruby On Rails Guide provides a neat example. I am using it as a template for my example
For example, you may want to store all users in a single database model, but be able to trace relationships such as between affiliate and users. This situation can be modeled with self-joining associations:
class User < ApplicationRecord
has_many :users, :through => :referrals
has_one :affiliate, :through => :referral
end
This allows me to keep both users and affiliate in a same database table which is correct because fundamentally they are all individual users.
Problem arises when the model grows. Affiliate has its own set of methods - earnings, expected_earnings etc. These methods are very specific to Affiliate and I have my qualm keeping them with other user methods.
Loading object in correctly named variable helps:
affiliate = User.find 1
affiliate.earnings # used in context of affiliate
user = User.find 1
user.subscriptions # mostly in context to user
But when I read the User model, Affiliate related methods feels out-of-place.
Is there a way to namespace these methods correctly? What is the standard way of organizing self join model methods?
One way to solve this is with Single Table Inheritance. Before accepting this approach, I would recommend searching the web for "single table inheritance rails" and reading up on the pros and cons of it. A lot of digital ink has been spent on this subject.
With the caveat out of the way, Single Table Inheritance (STI) allows you to let multiple Rails models share one database table. You do this by adding a string field called type to your database table. Rails will interpret this as the subclass of your model. You would then create several models that inherit from User.
In your specific case, the type field would either contain user or affiliate. You would also create an Affliliate class which inherits from User. All of your Affiliate specific methods would be put in the Affiliate class. Rails is smart enough to use the type field in the database to identify records from the appropriate class.
Here is the migration you would run:
class AddTypeToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :type, :string
add_index :users, :type
end
end
Next you would add an Affiliate class:
# app/models/affliliate.rb
class Affiliate < User
# Affiliate specific methods here.
end
You may also want to create a class for non-affiliate users. Call it customers:
# app/models/customer.rb
class Customer < User
# Customer specific methods here.
end
Use the appropriate class name when creating new records and rails will automatically populate the type field in the database.
You would then moving your associations to the appropriate model:
# app/models/affiliate.rb
class Affiliate < User
has_many :customers, through: :referrals, foreign_key: :user_id
end
# app/models/customer.rb
class Customer < User
has_one :affiliate, through: :referral, foreign_key: :user_id
end
I have not tested this, but it should work.
I have a Rails app in which the user can subscribe to addons, the list is dynamic and contains currently about 10 addons.
The list specifies which is on/off.
Each addon has a rather unique set of properties.
My current solution is that the application has 2 "parent" models and the one new model for each addon:
class AddonPrototype
has_many :addons
end
class Addon
belongs_to :addon_prototype
belongs_to :user
end
class AddonAlpha
belongs_to :addon
end
class AddonBeta
belongs_to :addon
end
etc..
The model AddonPrototype has one instance of each addon, with the default name as the only property.
The model Addon with the properties enabled, custom_name. When the user is visiting the page with addons, a check is done to see whether the user has an Addon instance for each existing AddonPrototype, or else creating one on the fly.
For each addon there is a unique model (e.g. AddonAlpha, AddonBeta etc), with the set of properties apt for each particular addon.
This design feels cumbersome, what could be a leaner setup?
I'm probably missing out on some details, so take this post with a grain of salt.
Currently, the naming of the models seems to be a bit misleading. The AddonPrototype is an actual addon, whereas the Addon model represents the act of a user having installed an addon.
Here's the structure I would go for:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :installations
has_many :addons, through: :installations
end
# app/models/addon.rb
class Addon < ActiveRecord::Base
has_many :installations
has_many :users, through: :installations
end
# app/models/installation.rb
class Installation < ActiveRecord::Base
belongs_to :addon
belongs_to :user
end
AddonPrototype has been renamed to Addon, and the old Addon model has been renamed to Installation (Subscription or something alike would also be a good name).
I would recommend against creating Installation records for each addon when a user visits the addons page. Just create these records when a user actually installs an addon. This keeps your code simpler and your database smaller.
I need to know, if there is a way to deal with different type of users/clients in Rails using a single model.
What I really need to do?
- I need to save a different type of clients in my database. So, I have this migration:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.string :name # Person name if "Personal", Company name if type "Company"
t.string :nif # fillable only if type is Company
t.string :identity_card_number # fillable only if type is Personal
t.integer :client_type # Type can be Personal or Company
t.references :company, index: true # if type is personal, it can belong to a company
t.timestamps null: false
end
end
end
Then I create this model
class Client < ActiveRecord::Base
has_many :employees, class_name: 'Client', foreign_key: 'company_id'
belongs_to :company, class_name: 'Client'
end
Note: A personal account can belong to a company or not.
Based on your experience, am I doing this in the right way? There are another way to do that?
EDIT:
Hi #manfergo25,
Whit this I have another question. "Company" and "Personal" are both "Clients Account", in that way, both must be able to buy services.
If I need to associent the client with the service, can I do this?
class Personal < Account
has_many :services
end
and
class Service < ...
belongs_to :account
end
??
The right way is Single Table Inheritance (STI) as Sontya say.
class Account < ActiveRecord::Base
end
Then,
class Client < Account
end
class Provider < Account
end
You only have to add a type column in 'Account' to contain a string representing the type of the stored object.
For example in a controller you could do this:
account = Client.find(params[:autocomplete_client])
params[:service][:account_id] = account.id
#service = Service.new(params[:service])
You can use STI(Single Table Inheritance)
class Account < ActiveRecord::Base
end
class Company < Account
has_many :services, :dependent => :destroy
end
class Personal < Account
has_many :services, :dependent => :destroy
end
class Service < ActiveRecord::Base
belongs_to: personal
belongs_to: company
end
With the above definition, a personal and company should be able to buy services.
and you should be able to call
#company.services # it will return you the number of services of company
#personal.services # it will return you the number of services of personal
Personally, I think the way you have suggested in your question is the way I'd do it.
While Single Table Inheritance is intellectually better modelling -- I've found STI to be a bit hard to work with and sometimes unreliable in Rails. And probably won't improve the cleanliness or conciseness of your code all that much in the end anyway. STI is good to keep in mind as an option, if you find the OP approach is not working well as far as allowing you to write clear concise code and it seems STI could work out better.
But I'd start without STI, with just the one Client class, as you've outlined in your question. If you later add STI, you'd still have the Client class, you'd just have sub-classes for, say, PersonalClient and CompanyClient. It won't be that hard to switch to STI later if you want to, although it might require some minor db schema alterations.
But I don't think it'll get you enough benefit to justify the added complexity, in an area of Rails that has sometimes had some rough edges.
Here's some more info about STI and it's plusses and minuses: http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/
I have a relationship between two models that is currently a many to many. I'd like to move it to a one to one relationship, because that's what all of the data has turned out to be.
Is there a seamless way to do this? Via a migration and model change combo? My models would be simple to just change as shown below:
Now
class App < ActiveRecord::Base
has_and_belongs_to_many :owners
end
class Owner < ActiveRecord::Base
has_and_belongs_to_many :apps
end
Changing to
class App < ActiveRecord::Base
belongs_to :owner
end
class Owner < ActiveRecord::Base
has_one :app
end
But how do I update the database to reflect these changes without losing any of the data I currently have?
Add an owner_id field to your apps table and then iterate through your apps_owners table and set the owner_id field in apps to the owner_id field in apps_owners for the apps record whose id is equal to app_id.
Assuming you're correct and there aren't multiple apps_owners entries for the same app_id following SQL should do it (not tested), although I'm not familiar with how to incorporate raw SQL into migrations:
update apps set owner_id=(select owner_id from apps_owners where apps.id = apps_owners.app_id)
You won't be able to do this simply. The main problem is that has_and_belongs_to_many relationships use a tertiary database table (apps_owners in your case) to hold the relationship information.
After you create the appropriate migration to add owner_id to your App model, you'll need to create a rake task that reads the app_owner table and re-creates the relationships.
I have the following models:
class Instance < ActiveRecord::Base
has_many :users
has_many :books
end
class User < ActiveRecord::Base
belongs_to :instance
end
class Book < ActiveRecord::Base
belongs_to :instance
end
I now want to add a BookRevision table, that has the following columns
(id, user_id, version # (auto increment), diff (old book copy),
timestamps)
Logic:
When a book is Created, a record is
added to the BookRevision table, so
we know who created the book in the
first place
When a book is Updated, a record is added with the user_id (could be
a different user), and a new version
, and the old book text, to serve as an archive.
Given that I have the Instance, User, Book table implement in my rails
add, are these the correct steps to make the above come to life?
- Add a migration for the BookRevision table....
rails generate migration AddTableBookRevision user_id:integer
version:integer diff:text
- Then update the models as follows:
class Instance < ActiveRecord::Base
has_many :users
has_many :books
end
class User < ActiveRecord::Base
belongs_to :instance
end
class Book < ActiveRecord::Base
belongs_to :instance
has_many :BookRevisions
end
class BookRevision < ActiveRecord::Base
belongs_to :Book
end
Then in my controller, when adding a new book? Right now I have:
#book = Book.create(params[:book].merge(:instance_id =>
current_user.instance_id))
How do I update that to account for the BookRevision association?
Thanks for helping me out!
You might want to check out something like acts_as_versioned instead of rolling your own. This works in the fashion you've described here, where modifications are saved into a separate but related table.
Keep in mind you will have to apply migrations to your Book and BookRevision table in parallel from that point forward. They must be schema compatible for revisioning to work.
I have built a version tracking system of this sort that used serialized models to avoid having to maintain migrations, as the intent was to preserve the exact state of the model regardless of future modifications via migrations. This has the disadvantage of not being able to roll back to an arbitrary older version because there may be a schema mis-match.