I am using concerns for my rails application. I've different kind of users so I have made a loggable.rb concern.
In my concern I have
included do
has_one :auth_info
end
because every of my user that will include the concern will have an association with auth_info table.
The problem is, what foreign keys I need to put in my auth_info table?
E.G
I've 3 kind of users:
customer
seller
visitor
If I had only customer, in my table scheme I would have put the field
id_customer
but in my case?
You can solve this with polymorphic associations (and drop the concern):
class AuthInfo < ActiveRecord::Base
belongs_to :loggable, polymorphic: true
end
class Customer < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Seller < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Visitor < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
Now you can retrieve:
customer.auth_info # The related AuthInfo object
AuthInfo.first.loggable # Returns a Customer, Seller or Visitor
You can use rails g model AuthInfo loggable:references{polymorphic} to create the model, or you can create the migration for the two columns by hand. See the documentation for more details.
Since user has roles 'customer', 'seller', 'visitor'.
Add a column called role to Users table.
Add a column called user_id to auth_infos table.
class AuthInfo < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :auth_info
end
you can do
user = User.first
user.auth_info
Now you a additional logic to your concerns.
Related
I have the following models:
user.rb
class User < ApplicationRecord
has_many :invitations
end
organization.rb
class Organization < ApplicationRecord
has_many :invitations
end
invitation.rb
class Invitation < ApplicationRecord
belongs_to :user
belongs_to :organization
end
I'm trying to query Active Record in the following way:
user = User.find(params[:id])
user.invitations.includes(:organization)
I want to be able to get all invitations for the user and also have the invitations include attributes of their related organization. However, I am only getting the invitation and none of the organization's attributes.
Even if I try:
Invitation.includes(:organization)
I'm still not getting each invitation's associated organization.
Any and all help is greatly appreciated!
includes method provides eager loading. This solves the N + 1 queries problem. You can access the loaded organization like user.invitations.first.organization. There will be no new queries here.
If you want to combine invitation and organization attributes, you can use joins and select.
user.invitations
.joins(:organization)
.select('invitations.*, organizations.foo, organizations.boo as blabla')
#demir has already answered your question.
Just as an addition - probably, you mixed up includes for models with include option of to_json \ as_json.
If you want to return some JSON result (e.g. in your API response), then you can do user.as_json(include: [invitations: { include: :organization }])
So, I"m not sure how to setup my associations. First, I have a User model (Devise) which has email and password.
class User < AR::Base
end
After that, I have multiple types of User models which contain more details information about the users:
class Doctor < AR::Base
belongs_to: User
end
And:
class Nurse < AR::Base
belongs_to: User
end
And:
class Therapist < AR::Base
belongs_to: User
end
So, I'm not sure how the User model should relate to the others. Is my design flawed?
Thanks for helping a noob.
The easiest way to implement what you are trying to achieve is to have a column on user to assign a role. So you can call the methods like this:
User.add_role(:doctor)
User.has_role?(:doctor)
You can do that using this gem https://github.com/mcrowe/roleable
Another way to implement it is by using ActiveRecord Enum:
http://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html The implementation would look like this:
User.role # => :doctor
User.doctor? # => true
User.therapist! # => true
User.role # => :therapist
I personally prefer using enums.
A complex way to do it would be to use polymorphism. Where you can put User as a polymorphic model. This blog post explains it in great detail. https://robots.thoughtbot.com/using-polymorphism-to-make-a-better-activity-feed-in-rails
Rails DB Associations Documentation Link
add these has_many's to user.rb
#user.rb
has_many :doctor
has_many :nurse
has_many :therapist
and You need to add user_id to doctor, nurse and therapist.
Such as like:
rails g migration add_user_id_to_nurses user_id:integer
rails g migration add_user_id_to_doctors user_id:integer
rails g migration add_user_id_to_therapits user_id:integer
Do not forget rake db:migrate at the end.
The best way to set setup a one-to-many association between these different types of users, with minimal duplicates, would be by setting up User to belong_to these other models Doctor, Nurse and Therapist
First, setup has_many association between these models to the User model
# app/models/doctor.rb
class Doctor < ActiveRecord::Base
has_many: :users
end
# app/models/nurse.rb
class Nurse < ActiveRecord::Base
has_many: :users
end
# app/models/therapist.rb
class Therapist < ActiveRecord::Base
has_many: :users
end
Then, add migrations to add doctor_id:integer, nurse_id:integer and therapist_id:integer to users table.
Then, setup belongs_to association to other ActiveRecord models.
# app/models/user.rb
class User < ActiveRecord::Base
belongs_to: :doctor
belongs_to: :nurse
belongs_to: :therapist
end
With this setup, you can access ActiveRecord data of these models as follows:
# get doctor associated to User.last
User.last.doctor
# get all the users who are patients of Doctor.last
Doctor.last.users
# get the nurse associated to User.last
User.last.nurse
# get all the users who are patients of Nurse.last
Nurse.last.users
I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }
I have two objects - User and Reviews. One User can write many reviews, but one review can be written by one user. Every user has a picture. I have:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
end
Why can't I do the following?
review.user.picture
You have to establish has_one/belongs_to associations on user/picture models and of course to have appropriate database migrations (foreign key user_id on picture table and foreign key user_id on review table).
Models look like:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
has_one :picture
end
class Picture
belongs_to :user
end
To try it, first create data in rails console:
user = User.create
review = Review.create user: user
picture = Picture.create user: user
Now you can find user picture if you have only reference to review object. Exit console and run again and type:
review = Review.last
review.user.picture
It returns picture object.
More info:
http://guides.rubyonrails.org/association_basics.html#choosing-between-belongs-to-and-has-one
Based on the info you provided, I'm assuming you still need to set up a foreign key in the database. The database migration for the the reviews table should look similar to this:
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.integer :user_id
t.timestamps
end
add_index :reviews, :user_id
end
end
Then you would be able to add a related user_id to a new review object. For instance, in rails console:
Review.new(user_id: 1)
Assuming that you defined picture as a column in your User table, you should be able to run review.user.picture without any issues.
Method
The bottom line is your picture method will not be present in your user model
You can either use an instance method to create some functionality in your User model, use an ActiveRecord Association, or use something like Paperclip to provide an appended object on your user object
Association
You'll need to create the correct association for your review & picture objects -
#app/models/user.rb
Class Review < ActiveRecord::Base
has_many :reviews
has_one :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
belongs_to :user
end
This will allow you to call:
#review = Review.find params[:id]
#picture = #review.user.picture
A bonus tip for this is if you wanted to ensure compatibility with the law of dementer, you could use the delegate method in your user model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :attribute, to: :picture, prefix: true
end
This will allow you to call the likes of:
#review.user.picture_name
Paperclip
If you're using a picture model to store images, you may wish to use the likes of Paperclip to give you the ability to use the functionality of images in your Picture model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :url, to: :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
has_attached_file :picture
end
These will all help you. Using this answer with Иван Бишевац's will help you profusely :)
I am totally confused about how I should go about "the rails way" of effectively using my associations.
Here is an example model configuration from a Rails 4 app:
class Film < ActiveRecord::Base
# A movie, documentary, animated short, etc
has_many :roleships
has_many :participants, :through => :roleships
has_many :roles, :through => :roleships
# has_many :writers........ ?
end
class Participant < ActiveRecord::Base
# A human involved in making a movie
has_many :roleships
end
class Role < ActiveRecord::Base
# A person's role in a film. i.e. "Writer", "Actor", "Extra" etc
has_many :roleships
end
class Roleship < ActiveRecord::Base
# The join for connecting different people
# to the different roles they have had in
# different films
belongs_to :participant
belongs_to :film
belongs_to :role
end
Given the above model configuration, the code I wish I had would allow me to add writers directly to a film and in the end have the join setup correctly.
So for example, I'd love to be able to do something like this:
## The Code I WISH I Had
Film.create!(name: "Some film", writers: [Participant.first])
I'm not sure if I'm going about thinking about this totally wrong but it seems impossible. What is the right way to accomplish this? Nested resources? A custom setter + scope? Something else? Virtual attributes? thank you!
I created a sample app based on your question.
https://github.com/szines/hodor_filmdb
I think useful to setup in Participant and in Role model a through association as well, but without this will work. It depends how would you like to use later this database. Without through this query wouldn't work: Participant.find(1).films
class Participant < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
class Role < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
Don't forget to give permit for extra fields (strong_parameters) in your films_controller.rb
def film_params
params.require(:film).permit(:title, :participant_ids, :role_ids)
end
What is strange, that if you create a new film with a participant and a role, two records will be created in the join table.
Update:
You can create a kind of virtual attribute in your model. For example:
def writers=(participant)
#writer_role = Role.find(1)
self.roles << #writer_role
self.participants << participant
end
and you can use: Film.create(title: 'The Movie', writers: [Participant.first])
If you had a normal has_and_belongs_to_many relationship i.e. beween a film and a participant, then you can create a film together with your examples.
As your joining model is more complex, you have to build the roleships separately:
writer= Roleship.create(
participant: Participant.find_by_name('Spielberg'),
role: Role.find_by_name('Director')
)
main_actor= Roleship.create(
participant: Participant.find_by_name('Willis'),
role: Role.find_by_name('Actor')
)
Film.create!(name: "Some film", roleships: [writer, main_actor])
for that, all attributes you use to build roleships and films must be mass assignable, so in a Rails 3.2 you would have to write:
class Roleship < ActiveRecord::Base
attr_accessible :participant, :role
...
end
class Film < ActiveRecord::Base
attr_accessible :name, :roleships
...
end
If you want to user roleship_ids, you have to write
class Film < ActiveRecord::Base
attr_accessible :name, :roleship_ids
...
end
Addendum:
Of cause you could write a setter method
class Film ...
def writers=(part_ids)
writer_role=Role.find_by_name('Writer')
# skiped code to delete existing writers
part_ids.each do |part_id|
self.roleships << Roleship.new(role: writer_role, participant_id: part_id)
end
end
end
but that makes your code depending on the data in your DB (contents of table roles) which is a bad idea.