I have a parent object, Post, which has the following children.
has_one :link
has_one :picture
has_one :code
These children are mutually exclusive.
Is there a way to use polymorphic associations in reverse so that I don't have to have link_id, picture_id, and code_id fields in my Post table?
I wrote up a small Gist showing how to do this:
https://gist.github.com/1242485
I believe you are looking for the :as option for has_one. It allows you to specify the name of the belongs_to association end.
When all else fails, read the docs: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one
Is there a way to use polymorphic
associations in reverse so that I
don't have to have link_id,
picture_id, and code_id fields in my
Post table?
has_one implies that the foreign key is in the other table. If you've really defined your model this way, then you won't have link_id, picture_id, and code_id in your Post table. I think you meant to say belongs_to.
I want to do something like
#post.postable and get the child
object, which would be one of link,
picture, or code.
I believe you could do this by using STI and combining the links, pictures, and codes tables, then testing the type of the model when retrieving. That seems kludgey though, and could end up with lots of unused columns.
Is there a reason for not storing the unused id columns, other than saving space? If you're willing to keep them, then you could define a virtual attribute and a postable_type column : (untested code, may fail spectacularly)
def postable
self.send(self.postable_type)
end
def postable=(p)
self.send(postable_type.to_s+"=",p)
end
Related
It's been a while since I've worked with Rails (Rails 6) on a new application. I'm failing to recall how to properly set up associations. I have two problems that are rather similar and may help to answer each other. I understand that the model containing belongs_to will hold the foreign key, however, it feels backward in my head.
I have a User model with a pronouns attribute. I have a Model/Table pronouns. A user can have one pronouns record associated with is (has_one?). But a pronouns record can belong to any number of users. So belongs_to won't work in this case, since the foreign key would be on the pronouns table. The way that I've sort of gotten around this is to use belongs_to, but this doesn't feel right because I need to make it optional since pronouns isn't required for a user.
class User < ApplicationRecord
belongs_to :pronouns, optional: true
end
class Pronoun < ApplicationRecord; end
I have the following models: Location, User, Experience, JobPost. In this case, User, Experience, and JobPost can have a single location, but Location can belong_to (have_many?) of each of these. This feels like a case for a polymorphic association. Where I'm feeling confused is what the has_one model (user, experience, job post) looks like. Do they have one? They shouldn't have many.
This is a work in progress, so there isn't much more code-wise I can really add. But if there are any areas where I can share more or elaborate, I'm happy to.
I have two models:
class Customer < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :customer
validates :customer, presence: true
end
Then, in my controller, I would expect to be able to create both in
"one" sweep:
#customer = Customer.new
#customer.contacts.build
#customer.save
This, fails (unfortunately translations are on, It translates to
something like: Contact: customer cannot be blank.)
#customer.errors.messages #=> :contacts=>["translation missing: en.activerecord.errors.models.customer.attributes.contacts.invalid"]}
When inspecting the models, indeed, #customer.contacts.first.customer
is nil. Which, somehow, makes sense, since the #customer has not
been saved, and thus has no id.
How can I build such associated models, then save/create them, so that:
No models are persisted if one is invalid,
the errors can be read out in one list, rather then combining the
error-messages from all the models,
and keep my code concise?
From rails api doc
If you are going to modify the association (rather than just read from it), then it is a good idea to set the :inverse_of option on the source association on the join model. This allows associated records to be built which will automatically create the appropriate join model records when they are saved. (See the ‘Association Join Models’ section above.)
So simply add :inverse_of to relationship declaration (has_many, belongs_to etc) will make active_record save models in the right order.
The first thing that came to my mind - just get rid of that validation.
Second thing that came to mind - save the customer first and them build the contact.
Third thing: use :inverse_of when you declare the relationship. Might help as well.
You can save newly created related models in a single database transaction but not with a single call to save method. Some ORMs (e.g. LINQToSQL and Entity Framework) can do it but ActiveRecord can't. Just use ActiveRecord::Base.transaction method to make sure that either both models are saved or none of them. More about ActiveRecord and transactions here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
I've got an association between models that is polymorphic.
Example:
class Review
belongs_to :review_subject, :polymorphic => true
end
However, I would like to make a call on this :review_subject association where I know all the results will be of a certain type.
In my case, I want to do this so I can join in the review_subject and then impose a condition upon it. Doing so on a polymorphic relation normally causes this to raise an EagerLoadPolymorphicError. The logic behind this is that it's not able to know what model to load in order to perform the join, but that does not apply in my case because I already know only one model will be involved in this query.
Example, where I know that all relevant reviews will belong_to a Book, and I want to only show reviews where the books have authors:
Review.joins(:review_subject)
.where(review_subject_type => "Book")
.where("reviewed.book_author IS NOT NULL")
Is there a way to temporarily disable the polymorphic relationship?
The best solution I've come up with is to add a second associationin the Review model, belongs_to :review_subject_books_only that is not polymorphic, and can be called on only in this situation. However, this is an ugly hack both in the model, and in that it also messes up include calls unless the views also refer to a Review's review_subject_books_only.
Do the query the other way around:
Book.joins(:reviews).where('book_author is not null')
I have three tables/models. User, Alliance and Alliance_Membership. The latter is a join table describing the :Alliance has_many :Users through :Alliance_Membership relationship. (:user has one :alliance)
Everything works ok, but Alliance_Membership now has an extra field called 'rank'. I was thinking of the best way to access this little piece of information (the rank).
It seems that when i do "alliance.users", where alliance is the user's current alliance object, i get all the users information, but i do not get the rank as well. I only get the attributes of the user model. Now, i can create a helper or function like getUserRole to do this for me based on the user, but i feel that there is a better way that better works with the Active Record associations. Is there really a better way ?
Thanx for reading :)
Your associations are all wrong - they shouldn't have capital letters. These are the rules, as seen in my other answer where i told you how to set this up yesterday :)
Class names: Always camelcase like AllianceMembership (NOT Alliance_Membership!)
table names, variable names, methods and associations: always underscored and lower case:
has_many :users, :through => :alliance_memberships
To find the rank for a given user of a given alliance (held in #alliance and #user), do
#membership = #alliance.alliance_memberships.find_by_user_id(#user.id)
You could indeed wrap this in a method of alliance:
def rank_for_user(user)
self.alliance_memberships.find_by_user_id(user.id).rank
end
My application has about half a dozen different types of items that a user can comment on (articles, photos, videos, user profiles, blog posts, forum posts).
My plan right now is to have a single comments table in my database and then have parent_id and type fields in the table.
The type field would just be a string and the contents of it would be the name of it's parent table. So for an Article comments, the type would be article, for example.
Is that the best way to handle the comments table? Or is there some other, more efficient way to do that?
Using a polymorphic association would be the best way to achieve this - check out Ryan's railscast on the subject (or the ASCIIcast at http://asciicasts.com/episodes/154-polymorphic-association). I think you'll end up with something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
belongs_to :user
end
Then each model you want users to be able to comment on will have the line:
has_many :comments, :as => :commentable
I also found this post useful when setting up polymorphic comments. Hope that helps!
What you are describing is a called a polymorphic association. Rails can handle that out of the box, see this episode:
http://railscasts.com/episodes/154-polymorphic-association
If a comment can't apply to multiple things (the same comment can't apply to both an article and a blog post, or two different articles) then why is it a base entity?
If you're committed to it, I'd have a comment table that looked like this:
COMMENT_ID
COMMENT_BODY
USER_ID
DATE
ARTICLE_ID references ARTICLE on delete cascade
BLOG_POST_ID references BLOG_POST on delete cascade
... etc
And then have constraint that says one and only one of the parents can apply.
An alternative is to have a COMMENT table for each base entity, so you'd have ARTICLE_COMMENTS, BLOG_POST_COMMENTS, and so forth.
I'd suggest looking at plugins like acts_as_commentable, and looking at how they are implemented. Or just use the plugin.
I think your design is pretty good. It is simple and easy to implement. The only requirement would be that the data-types of the row identifiers would have to be the same as comment.parent_id so that your joins are consistent. I would actually define views on the comments table for each 'type'. For example, "create photo_comments as select * from comments where type = 'PHOTOS'". then you could join from PHOTOS to PHOTO_COMMENTS on PHOTOS.PHOTO_ID = PHOTO_COMMENTS.PARENT_ID, and so on.
You could also create a supertype say, widget, and each of your photo, blog_post etc. could be sub-types of widget. Then you could constrain your comments table to have an FK to the widget table. The 'type' column could then be moved to the widget table.