Creating the reverse of an association within a concern - ruby-on-rails

I have a concern that creates an association:
# concerns/product.rb
module Product
extend ActiveSupport::Concern
included do
has_many :products, class_name: "Product", foreign_key: :owner_id
end
end
And an example model:
# models/user.rb
class User < ApplicationRecord
include Product
end
If I do: User.products.last it works fine. I also want to be able to do Product.last.owner but it won't work without the association being defined. I can't define it in the Product model since I have no clue to what models will include the concern that creates the association.
I have tried creating the association using inverse_of:
class Product < ApplicationRecord
belongs_to :owner, inverse_of: :products
end
... but it won't work, apparently it needs a class name.
I've also tried reflecting on which classes the concern gets included within but it raises some strange errors when the concern gets included into several different models.
How do I create the inverse of an association from within the concern?

As pointed out by #engineersmnky you can't actually setup an association that points to a bunch of different classes without using polymorphism:
# you can't have a class and a module with the same name in Ruby
# reopening the class/module will cause a TypeError
module ProductOwner
extend ActiveSupport::Concern
included do
has_many :products, as: :owner
end
end
class Product < ApplicationRecord
belongs_to :owner,
inverse_of: :products,
polymorphic: true
end
class User < ApplicationRecord
include ProductOwner
end
The information about what you're actually joining has to be stored somewhere on the products table. If you don't use polymorphism you just have an integer (or UUID) which will always refer to a single table.

Related

error with polymorphism and jsonapi-resources

I'm trying to setup a polymorphic association using the jsonapi-resources gem in Rails 5.
I have a User model that has a polymorphic association called profile, which can be of type Inspector or Buyer. Here are the truncated models:
class User < ApplicationRecord
belongs_to :profile, polymorphic: true
end
class Inspector < ApplicationRecord
belongs_to :user
end
class Buyer < ApplicationRecord
belongs_to :user
end
In the users table, there are corresponding profile_id and profile_type fields to represent the polymorphic association to inspectors and buyers. This all works as expected in our current Rails setup but I'm running into errors when trying to set this up for JSON:API using jsonapi-resources.
And now the corresponding jsonapi-resources resources and controllers (according to the directions):
class Api::V1::Mobile::UserResource < JSONAPI::Resource
immutable
attributes :name, :email
has_one :profile, polymorphic: true
end
class Api::V1::Mobile::ProfileResource < JSONAPI::Resource
end
class Api::V1::Mobile::ProfilesController < Api::V1::Mobile::BaseController
end
As far as I can tell, everything should now be setup properly but I get the following error when hitting the endpoint:
"exception": "undefined method `collect' for nil:NilClass",
"backtrace": [
".rvm/gems/ruby-2.6.5/gems/jsonapi-resources-0.10.2/lib/jsonapi/relationship.rb:77:in `resource_types'",
When digging into relationship.rb mentioned in the stack trace it looks like it can't get resolve the polymorphic types, so I tried the following:
class Api::V1::Mobile::UserResource < JSONAPI::Resource
immutable
attributes :name, :email
has_one :profile, polymorphic: true, polymorphic_types: ['inspector', 'buyer']
end
But alas, another error: Can't join 'User' to association named 'inspector'; perhaps you misspelled it?
Thanks in advance for any help with getting this setup!
The core problem actually has nothing to do with jsonapi-resources and is the associations. The inverse side of a belongs_to resource is always a has_one or has_many which points to the foreign key on the other table (and vice-versa).
class User < ApplicationRecord
belongs_to :profile, polymorphic: true
end
class Inspector < ApplicationRecord
has_one :user, as: :profile
end
class Buyer < ApplicationRecord
has_one :user, as: :profile
end
Having two belongs_to associations that point to each other would mean that you would have foreign keys on both sides - which is bad DB design due to the duplication (there should be only one source of truth) and won't really work in ActiveRecord since it will only ever write the foreign key on one side when you associate two models.

Linking many existing models to one new one. Ruby on Rails

So I am making an app that reviews books, articles and the like.
I have created the backbone of the app by creating models, views, controllers etc for Piece(the book or article), Section(self explanatory), Subsection, and Subsubsection.
I want to add a new model into the mix, a "Links" model (which will just be a link to another source or website). My issue is that I don't know how to make ALL of my previously stated models have "Links". I want each of The above models to have access and CRUD capabilities to their "Links", but so far all i have read about is has_many or has_and_belongs_to_many.
As far as I understand, those kinds of relations only relate ONE model to ONE other model, even if Piece might have many Sections, it only relates these two models.
I guess the Links model would have to have an obligatory piece_id, but then optional id's such as: section_id, subsection_id depending on where the link was. So if in Chapter 3 of my first book i want to add a link, it would have an obligatory piece_id=1 and then a section_id=3, but then no subsection_id or subsubsection_id.
So how do I go about creating a model such that it belongs to several other models? Or is this even possible?
https://github.com/kingdavidek/StuddyBuddy
Ok, it sounds like essentially you want a polymorphic association
class Link
belongs_to :linkable, polymorphic: true
end
class Piece
has_many :links, as: :linkable
end
Link would need linkable_id integer column and linkable_type string column. You can then use it in the same way as an ordinary has_many to belongs_to association
if i wanted to create a new Link in a Subsection, it would belong to
Subsection, but also to Section and Piece because of the nested
relationship
This bit rails can't help with, you'd need to write your own method to find all the links in the chain of items.
This is a pretty good use case for polymorphic associations. For simplicity lets start out with a one to many relationship:
class Link < ActiveRecord::Base
belongs_to :linkable, polymorphic: true
end
class Piece < ActiveRecord::Base
has_many :links, as: :linkable
end
class Section < ActiveRecord::Base
has_many :links, as: :linkable
end
Here the links table will have linkable_id (int) and linkable_type (string) columns. One important thing to take note of here is that linkable_id is not a true foreign key from the RBDMS point of view. Rather ActiveRecord resolves which table the relation points to when it loads the relation.
If we want to cut the duplication we can create a module which contains the desired behavior. Using ActiveSupport::Concern cuts a lot of the boilerplate code involved in creating such a module.
class Link < ActiveRecord::Base
belongs_to :linkable, polymorphic: true
end
# app/models/concerns/linkable.rb
module Linkable
extend ActiveSupport::Concern
included do
has_many :links, as: :linkable
end
end
class Piece < ActiveRecord::Base
include Linkable
end
class Section < ActiveRecord::Base
include Linkable
end
So how would we make a polymorpic many to many relation?
class Link < ActiveRecord::Base
has_many :linkings
end
# this is the join model which contains the associations between
# Links and the "linkable" models
class Linking < ActiveRecord::Base
belongs_to :link
belongs_to :linkable, polymorphic: true
end
# app/models/concerns/linkable.rb
module Linkable
extend ActiveSupport::Concern
included do
has_many :links, through: :linkings, as: :linkable
end
end
class Piece < ActiveRecord::Base
include Linkable
end
class Section < ActiveRecord::Base
include Linkable
end
On a side note - a better way to build a hierarchy between sections would be to use a single Section model and give it a self joining relationship.
class Section < ActiveRecord::Base
belongs_to :parent, class_name: 'Section'
has_many :children, class_name: 'Section',
foreign_key: 'parent_id'
end

ActiveRecord won't build the right class using STI

I'm using single table inheritance in my application and running into problems building inherited users from an ancestor. For instance, with the following setup:
class School < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
attr_accessible :type #etc...
belongs_to :school
end
Class Instructor < User
attr_accessible :terms_of_service
validates :terms_of_service, :acceptance => true
end
Class Student < User
end
How can I build either a instructor or student record from an instance of School? Attempting something like School.first.instructors.build(....) gives me a new User instance only and I won't have access to instructor specific fields such as terms_of_service causing errors later down the rode when generating instructor-specific forms, building from console will give me an mass-assignment error (as it's trying to create a User record rather than an Instructor record as specified). I gave the example of School, but there are a few other associations that I would like to inherit from the User table so I don't have to repeat code or fields in the database. Am I having this problem because associations can not be shared in an STI setup?
You should specify instructors explicitly
class School < ActiveRecord::Base
has_many :users
has_many :instructors,:class_name => 'Instructor', :foreign_key => 'user_id'
end
And what else:
class School < ActiveRecord::Base
has_many :users
has_many :instructors
end
class Instructor < User
attr_accessible :terms_of_service # let it be at the first place. :)
validates :terms_of_service, :acceptance => true
end
OK it seems part of the problem stemmed from having the old users association inside of my School model. Removing that and adding the associations for students and instructors individually worked.
Updated School.rb:
class School < ActiveRecord::Base
#removed:
#has_many :users this line was causing problems
#added
has_many :instructors
has_many :students
end

How to set up a polymorphic has_many :through?

I'm having trouble figuring out the right way to set this association up.
I have 3 models: Musicians, Bands and Managers. Each of those can have a certain "level" associated with them (stress, happiness, etc).
But I'm not sure how to correctly associated my levels with the other 3.
There needs to be an intermediary model that connects the levels with the different items and sets what the current level is at.
Do I need some sort of has_many :through that's polymorphic? And how on earth do I set that up? Is there some other type of associated I need?
I asked a similar question before but did a thoroughly awful job of explaining what I was trying to do, so this is my second attempt.
ItemLevels needs a type to know what it is associated with:
class ItemLevel < ActiveRecord::Base
# levelable_id :integer
# levelable_type :string(255)
belongs_to :levelable, :polymorphic => true
has_many :levels
end
class Musician < ActiveRecord::Base
has_many :levelables
end
class Musician < ActiveRecord::Base
has_many :levelables
end
class Manager < ActiveRecord::Base
has_many :levelables
end
Level then only needs to know what ItemLevel it is associated with:
class Level < ActiveRecord::Base
belongs_to :item_level
end
If you want to be strict, you should put some validation in you Level model to check it has an ItemLevel and the type being instantiated.

Cannot get data from related models in Rails

I have 2 models in different namespace.
class Admin::Membership < ActiveRecord::Base
has_many :authorization_roles
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership
end
The Membership model is in different folder with AuthorizationRole model (I don't know what is called)
When run Admin::Membership.find(:all), the data from AuthorizationRole model is not included. I've create membership_id field on authorization_roles table, but I still can't get both models related. Is something wrong in this code? Sorry if I'm missing something basic here.
Try this
class Admin::Membership < ActiveRecord::Base
has_many :authorization_roles, :class_name => '::AuthorizationRole'
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership, :class_name => 'Admin::Membership'
end
I've never used namespaced models and I don't think you need to... but maybe you'll have to specify the class name in AuthorizationRole, something like:
belongs_to :membership, :class_name => 'Admin::Membership'
UPDATE:
Assuming you have:
class Membership < ActiveRecord::Base
has_many :authorization_roles
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership
end
You have added an integer column called membership_id to authorization_roles and you've run the migrations. Now you should be able to create authorization_roles like this #membership.authorization_roles.create( ... ) and fetch them #membership.authorization_roles
Check to see if you are setting the table name prefix. The Rails model generator adds a file like this for namespaced models:
# /app/models/admin.rb
module Admin
def self.table_name_prefix
'admin_'
end
end
Note: this is Rails version 3.0.1 -- not sure about earlier versions.

Resources