In my web dev class we were given the following code:
class User < ActiveRecord::Base
has_many :shouts
has_many :followed_user_relationships, class_name: "FollowingRelationship"
has_many :followed_users, through: :followed_user_relationships
def follow(other_user)
followed_users << other_user
end
end
I understand everything except what class_name does. Does it add information to the model relationship somehow or does it just make this relationship an alias of that class name? I checked out the documentations and it seemed pretty worthless for someone new to rails.
Rails needs to know which class to instantiate for an association. It does this by guessing based on the association name, but it can only do this when your class and association are named predictably. Specifically, it uses String#classify to turn an association name into a class name. classify converts from underscore case to camel case, and singularizes the word:
"some_kind_of_records".classify => "SomeKindOfRecord"
In your particular case, your association name and class name aren't related this way.
:followed_user_relationships would cause Rails to look for a class called FollowedUserRelationship, which isn't the right class, FollowingRelationship.
Because you've deviated slightly from this convention, you have to explicitly tell Rails the name of the class involved. That's all class_name does. It tells Rails the name of the class to use, when Rails isn't able to guess correctly.
Consider a simpler example:
class User < ActiveRecord::Base
end
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
end
How could Rails know that a Post is associated with a User, if all we had written was belongs_to :author?
Related
I have a polymorphic table in rails MetaFieldsData which also belongs to a table MetaFields
class MetaFieldsData < ApplicationRecord
belongs_to :owner, polymorphic: true
belongs_to :meta_field
end
class MetaField < ApplicationRecord
belongs_to :organization
has_many :meta_fields_data
end
One model which is connected to the polymorphic table is called orders:
class Order < ApplicationRecord
belongs_to :organization
...
has_many :meta_fields_data, as: :owner
...
owner is my association class (the same what is imageable from the official RoR guide)
Now I see a strange behaviour when I want to create a record on a the Order model:
MetaFieldsData.create(owner: order, meta_field: some_meta_field)
It throws:
NameError Exception: Rails couldn't find a valid model for MetaFieldsDatum association.
Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
What is weird is that there is no class MetaFieldsDatum (note the typo here, coming from Rails). I searched all my code and there is no typo in there, also not in the class name definition.
This makes it impossible for me to create an actual MetaFieldsData on this table as Rails seems to interpret the naming wrong.
What could possibly be wrong here?
Thank you
I had the same problem, but my solution was different.
I had a typo in my belongs_to model which invalidated the model.
I discovered it was an invalid model by trying to access it in the console. Because it was invalid, Rails didn't load it and subsequently couldn't find it.
The error disappeared when I corrected the typo.
Datum is used as a plural form of data. Notice that you have has_many :meta_fields_data and if you would to change that into a singular it would also be has_one :meta_fields_data.
This is called inflection and it is a way of detecting plural forms of words, you can read up on how rails does it here https://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html
In general you can either simply obide to what the error tells you and use datum in relationship name (specify class_name if you do so), or define your own inflection
Lately I've grown weary of littering my app/models directory with pointless boilerplate models such as:
Join models that always contain a couple belongs_tos and nothing else.
Status log models that just include SomeConcern and make a couple macro calls.
Revision tracking models that again, just include a concern and call a macro.
These models only exist to support has_many and has_many ... through: associations.
Adding model concerns that generate these models as needed clears simplifies the app/models directory. So instead of:
has_many :model_things
has_many :things, through: :model_things
and a trivial app/models/model_thing.rb that says:
class ModelThing < ApplicationRecord
belongs_to :model
belongs_to :thing
end
I can have a ThingSupport concern with a has_things macro that:
Creates the has_many :model_things association based on the class name and some options to has_things.
Creates the has_many :things, through: :model_things association.
Find or create the Model::Thing (see below for why this name is used) class with a call like:
ModuleUtil.find_or_create(join_model_name) do
Class.new(ApplicationRecord) do
# Set the table name, call belongs_to as needed, call concern methods, ...
end
end
where ModuleUtil.find_or_create is a simple method that uses String#constantize to find the desired module (if it exists) or create it using the block and Object#const_set if it can't be found.
All the model and association names can be built using the usual Rails conventions from the caller's class name and some options to has_things for special cases.
The question is am I playing with fire here? What can go wrong with this sort of chicanery?
One problem that I've already come across is that the model classes that are generated don't exist on their own so they cannot be directly referenced from an ActiveJob (such as a deliver_later mailer). For example, if loading Model creates the ModelThing association model then you can't reference a ModelThing in a mailer argument because ActiveJob won't know that you have to load the Model class before ModelThing exists. However, this can be solved by using Model::Thing instead so that constantize will look for Model (and find it in app/models/model.rb) before trying to find Model::Thing (which will exist because constantize will have just loaded Model which creates Model::Thing). Am I missing something else?
I have no idea if I'm following you or not. So, if this is way off target, please say so and I'll delete.
Focusing in on the join model bit, I also got tired of that flim flam. So, I created a model like:
module ActsAsHaving
class HasA < ActiveRecord::Base
validates :haser_type, :haser_id, :hased_type, :hased_id, presence: true
belongs_to :hased, polymorphic: true
belongs_to :haser, polymorphic: true
acts_as_taggable
def haser=(thing)
self.haser_type = thing.class.name
self.haser_id = thing.id
end
def haser
haser_type.constantize.find_by(id: haser_id)
end
def hased=(thing)
self.hased_type = thing.class.name
self.hased_id = thing.id
end
def hased
hased_type.constantize.find_by(id: hased_id)
end
end
end
I didn't use the built-in accessors and validations because I sometimes use this to join non-AR records (which I grab from remote API services some of which belong to me and some of which don't but that's a longer story).
Anyway, I then wrote an acts_as_having macro that let me do stuff like:
class Person < ActiveRecord::Base
acts_as_having :health_events, class_name: "Foo::Event", tag_with: "health_event", remote: true
acts_as_having :program_events, class_name: "Foo::Event", tag_with: "program_event", remote: true
acts_as_having :email_addresses, :phone_numbers, :physical_addresses
end
Which gives me stuff like:
#person.email_addresses
#person.email_addresses << #email_address
etc...
I can do the inverse like:
class EmailAddress < ActiveRecord::Base
acts_as_had_by :person
end
Which gives me stuff like:
#email_address.person
etc...
Then, I wrapped all that junk up into a gem. Now I rarely create join models unless they have some specific requirements that I can't shoe horn into my acts_as_having bit.
Anyway, I don't know if it's playing with fire or not. I don't even know if I'm making sense or addressing your concept. But, I started my gem about three years ago and I haven't regretted it. So, there's that.
Hey guys I broke a bit of rails convention by using underscores instead of camelcasing on a class name (Ui_1_Log instead of Ui1Log) and now when I refer to the association it isnt finding the right class. For example:
class Ui_1_Log < ActiveRecord::Base
belongs_to :account
end
class Account < ActiveRecord::Base
has_many :ui_1_logs
end
Now when I call the association in the rails console it doesnt work because it returns a classname as Camelcase when it needs to keep the underscores...
x = Account.first
x.ui_1_logs.first #returns nameError: uninitialized constant Account::Ui1Log
It needs to be Account::Ui_1_Log but I don't know how to force that...Any ideas?!
You can set the class name on the association with class_name option.
class Account < ActiveRecord::Base
has_many :ui_1_logs, class_name: 'Ui_1_Log'
end
But I still recommend to follow conventions.
More specifically, let's say I have a model
class User < ActiveRecord::Base
:has_many => (xxxImages)
end
Where xxx can be one of different models in my application. For example:
class ABCImages < ActiveRecord::Base
:belongs_to => User
end
class EFGImages < ActiveRecord::Base
:belongs_to => User
end
What I'm basically asking is: is there any way to pick one of those models at runtime to be inserted into the User models has_many association? Or do I need to take the polymorphic route (which I've only read about slightly so I'm not too familiar with it yet)
Thanks!
I think Single Table Inheritance is the way you should go which will harness the power of having different models but rather store in the same table. The only thing needed for this is to add a database field called :type.
So, in you case, I would create a table names base_images and other two classes would be just a subclass of this class.
So, the migration for this base class would look like this,
class CreateBaseImages < ActiveRecord::Migration
def change
create_table :base_images do |t|
t.string :type
t.string :url
t.references :user
t.timestamps
end
end
end
Now, after you migrate this, it will create a model base_image.rb, as below,
class BaseImage < ActiveRecord::Base
belongs_to :user
end
That's it. Now, that we have the BaseImage we will create two different models namely AbcImage and EfgImage which would inherit from BaseImage class.
class EfgImage < BaseImage
end
class AbcImage < BaseImage
end
And our user class would look like this,
class User < ActiveRecord::Base
has_many :base_images
has_many :abc_images
has_many :efg_images
end
With this code in place, you can create association called abc_images or egf_images to user through association which works like a single table. And if you were to call base_images, it would fetch all the images, irrespective of which subclasses they belong.
You will find this so much resuable that later if you intend to create hij_images association then creating class HijImage and inheriting it from the BaseImage class and it simply works. By, the way there is no magic here, rails stores the name of the class into the type field in database. And so when you query for a certain class, it creates the predicate with type and the name of the class.
I was hoping I could inherit off an activerecord model, and use the subclass as I could the parent class. This does not appear to be the case, the AR relationships do not appear to work for the subclass.
class Manager < User
belongs_to :shop
end
class Shop < ActiveRecord::Base
has_many :managers
end
class PremiumShop < Shop
end
and
#premium_shop = manager.shop # Finds the shop.
#premium_shop = manager.premium_shop # Does not find the shop, NilClass:Class error
Is it possible to make this work?
The shop method exists for some instance of the Manager class because of the association you defined through the belongs_to. You have no premium_shop method defined on your Manager model, thus the NilClass error.
If you want to define such an association for the PremiumShop class, you must explicitly specify this.
belongs_to :premium_shop, class_name: "PremiumShop", foreign_key: :shop_id
Depending on your needs, you might also consider researching "rails single table inheritance".