rails - belongs_to any different model - ruby-on-rails

I have some models in my database:
- customer
has_many documents
- charts
has_many documents
- pages
has_many documents
Any of models above can have many documents.
How can I do this in the Document model? Is there any relationship can accept different models?

Yes, it is possible. This concept is called polymorphic association and can be done like this using Ruby on Rails:
class Document < ActiveRecord::Base
belongs_to :owner, polymorphic: true
class Customer < ActiveRecord::Base
has_many :documents, as: :owner
It uses 2 columns to work: one column to save the owner's type, and a second column to save th owner's id:
Document.create(owner_type: 'Customer', owner_id: customer.id)
Then, you can call the method .owner on the document object:
doc = Document.first
doc.owner # => Can either return a Customer, Chart or Page record
You might want to add some security around this, something to prevent from creating documents for a owner that is not supposed to have this relation:
class Document < ActiveRecord::Base
belongs_to :owner, polymorphic: true
validates :owner_type, inclusion: { in: %w( Customer Chart Page ) }
This will prevent from creating documents like this:
Document.create(owner_type: 'kittyCat', owner_id: 77) # won't work

Related

Rails save active record form through the associations

I have these models in my Rails app. My use-case is multilingual data. First I want to store the default language version, then it will be possible to add other language data.
class Category < ApplicationRecord
has_many :category_versions
accepts_nested_attributes_for :category_versions, allow_destroy: true
end
class CategoriesVersion < ApplicationRecord
belongs_to :categories
has_many :categories_contents
has_one :langs
end
class CategoriesContent < ApplicationRecord
belongs_to :categories_versions
end
And I would like to save the record with one toplevel record with associated record in CategoriesVersion and CategoriesContent.
Using data from params and generated inside create method in the controller.
I got first and second table, but I don't know how to add attributes for third table, nested in second one, if it is possible. So the association from CategoriesVersion to CategoriesContent
category_data = { category: {
kind: "user",
featured_image: "test",
featured_image_title: "test",
editorial_id: nil,
category_versions_attributes: [
{lang_id: Lang.find_by_lang("francais").id},
{published: false},
{featured: false} #data["featured"] || false
]
}}
test = Category.create(category_data["category"])
Please any suggestions?
If I understand your issue, I think accepts_nested_attributes_for is the configuration that you need in your models to save the nested data just saving the category.

Rails model associations - has_one or single table inheritance?

I'm having trouble deciding between Single Table Inheritance and a simple has_one relationship for my two models.
Background: I'm creating a betting website with a "Wager" model. Users may create a wager, at which point it is displayed to all users who may accept the wager if they choose. The wager model has an enum with three statuses: created, accepted, and finished.
Now, I want to add the feature of a "Favorite Wager". The point of this is to make it more convenient for users to create a wager, if they have ones they commonly create. One click instead of ten.
FavoriteWagers exist only as a saved blueprint. They are simply the details of a wager -- when the User wants to create a Wager, they may view FavoriteWagers and click "create", which will take all the fields of the FavoriteWager and create a Wager with them. So the difference is that FavoriteWagers acts as only as a storage for Wager, and also includes a name specified by the user.
I read up on STI, and it seems that a lot of examples have multiple subclassing - eg. Car, Motorcycle, Boat for a "Vehicle" class. Whereas I won't have multiple subclasses, just one (FavoriteWager < Wager). People have also said to defer STI until I can have more classes. I can't see myself subclassing the Wagers class again anytime soon, so that's why I'm hesitant to do STI.
On the other hand, has_one doesn't seem to capture the relationship correctly. Here is an example:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, dependent: destroy
end
Class FavoriteWager < ApplicationRecord
has_one :wager
belongs_to: user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
belongs_to :favorite_wager, optional: true
belongs_to :user
end
I've also thought about just copying the fields directly, but that's not very DRY. Adding an enum with a "draft" option seems too little, because I might need to add more fields in the future (eg. time to auto-create), at which point it starts to evolve into something different. Thoughts on how to approach this?
Why not just do a join table like:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, through: :favorite_wagers
end
Class FavoriteWager < ApplicationRecord
belongs_to :wager, index: true, foreign_key: true
belongs_to :user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
has_one :favorite_wager, dependent: destroy
has_one :user, through: :favorite_wager
end
Your FavoriteWager would have the following fields:
|user_id|wager_id|name|
That way you can access it like:
some_user.favorite_wagers
=> [#<FavoriteWager:0x00007f9adb0fa2f8...
some_user.favorite_wagers.first.name
=> 'some name'
some_user.wagers.first.amount
=> '$10'
some_user.wagers.first.favorite_wager.name
=> 'some name'
which returns an array of favorite wagers. If you only want to have ONE favorite wager per user you can tweak it to limit that. But this gives you the ability to have wagers and users tied together as favorites with a name attribute. I don't quite understand your use case of 'a live wager never has a favorite' but that doesn't matter, you can tweak this to suit your needs.

Use nested attributes and hierarchies

I have 2 tables is
- Training_Assessments : id, name
- Standards: id, name, training_assessment_id, parent_id
And Model Standards
class Standard < ApplicationRecord
ATTRIBUTE_PARAMS = %i(id name proportion parent_id).freeze
belongs_to :training_assessment
has_closure_tree
end
Model TrainingAssessment
class TrainingAssessment < ApplicationRecord
ATTRIBUTE_PARAMS =
[
:id, :name,
standards_attributes: Standard::ATTRIBUTE_PARAMS
].freeze
has_many :standards, dependent: :destroy
accepts_nested_attributes_for :standards, allow_destroy: true
end
I one training assessments have many standards, and each standard have many child standard have parent_id of parent standard. I try created at console but child standard don't have id of training assessments nested.
So how i can do it ? And how is the params structure?
You need an association from one standard to it's parent standard in your model:
belongs_to :parent_standard, foreign_key: :parent_id
Then you can delegate:
delegate :training_assessment, to: :parent_standard
EDIT:
Using closure_tree gem: child.root.training_assessment_id is the parents training_assessment_id

How to create an association that sets join table attributes automatically?

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.

Rails 3: What associations should I use to create model relationships

I am creating an app for uploading and sharing files between users.
I have User and Files models and have created a third File_Sharing_Relationships model which contains a sharer_id, file_id and shared_with_id columns. I want to be able to create the following methods:
#upload.file_sharing_relationships - lists users that the file is shared with
#user.files_shared_with - lists files that are shared with the user.
#user.files_shared - lists files that the user is sharing with others
#user.share_file_with - creates a sharing relationship
Are there any rails associations, such as 'polymorphic' that I could be using to make these relationships?
Any suggestions appreciated. Thanks.
All you need to do is to read Rails Guides and apply all what you learn.
Basically you need to store info about:
user who created a "sharing"
user or group or whatever is a target of a sharing action
resource that is being shared
So:
class SharedItem < ActiveRecord::Base
belongs_to :sharable, :polymorphic => true #this is user, please think of better name than "sharable"...
belongs_to :resource, :polymorphic => true #can be your file
belongs_to :user
end
You need SharedItem to have:
user_id: integer, sharable_id: integer, sharable_type: string, resource_id: integer, resource_type: string
Then you can get "methods" you specified by writing named scopes like:
named_scope :for_user, lambda {|user| {:conditions => {:user_id => user.id} }}
or by specifying proper associations:
class File < ActiveRecord::Base
has_many :shared_items, :as => :resource, :dependent => :destroy
end
I think you should create relationships something like this:
class User
has_many :files
has_many :user_sharings
has_many :sharings, :through => :user_sharings
end
class File
belongs_to :user
end
class Sharing
has_many :user_sharings
has_many :users, :through => :user_sharings
end
class UserSharing
belongs_to :user
belongs_to :sharing
end
.. this is very basic model of relations(It is just my point of view :)). User can have many sharings and also belongs to sharings. You can set file id to UserSharing table when you create user and it's share. And then you can create methods, you listed above, as scopes in proper models. I hope I helped you a little.

Resources