In my Rails application I have two classes :
UserPost and GroupPost (inheriting from ApplicationRecord), that I want to make subclasses of a new class, Post.
What would be the best way to add this in?
Is it as simple as making the class manually, and adding in the inheritance or are there problems that could cause?
I'm making the assumption that UserPost and GroupPost are inherited from ActiveRecord::Base, i.e. you want to save them in your DB
If you have class UserPost and GroupPost inherit from Post in the following way, this is known as Single Table Inheritance
class Post < ApplicationRecord
end
class GroupPost < Post
end
class UserPost < Post
end
Rails will, by default, expect you to have a column called "type" (can be configured to any other name) in the Posts table. GroupPost and UserPost will populate their respective values in the same table, adding in "GroupPost" or "UserPost" to the type. You can read up on it here
Rails also provides you the ability to have the parent class be "abstract", i.e. it shouldn't be able to persist to the database. Perhaps you noticed it in the the app/models/application_record.rb file as well. This way the parent serves as just a concept where you can add in common behaviour, STI will not kick in. There will need to be separate tables for children.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
If you plan on having separate tables for the classes, I'd recommend trying composition instead of inheritance using mixins. Basically you write common functionality in a "module" and include it to the classes that need the separate functionality. Concerns are a nice way to append class methods, associations etc without having to rely on arcane ruby syntax. I'd recommend having a look at the documentation
module Fooable
extend ActiveSupport::Concern
included do |base|
scope :enabled, -> { where(enabled: true) }
has_many :boos, dependent: :destroy
end
class_methods do
def having_name
where('name ILIKE ?', "#{name}%")
end
end
def hi
return 'hi'
end
end
class UserPost < ApplicationRecord
include Fooable
end
UserPost.first.boos # relationship
UserPost.enabled # calling a scope
UserPost.having_name('name') # calling a class method
UserPost.new.hi #calling an instance method
Related
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.
I have an application where there are multiple ways to handle creating and updating an object based on the user that is logged in. I keep getting a circular reference exception on the child classes. Is this correct or would it be better to create concerns module with shared code and have them both be independent?
class WorkOrder < ActiveRecord::Base
#parent class
end
module Dispatch
class WorkOrder < ::WorkOrder
#child class
end
end
module Service
class WorkOrder < ::WorkOrder
#child class
end
end
How are you planning on differentiating records in the database between being from the parent class or one of the child classes?
You may want to look at Single Table Inheritance (STI) and Polymorphic Associations:
http://railscasts.com/episodes/394-sti-and-polymorphic-associations
I have a Product table, which has multiple fields like product price, product manufacturer and so on which is common in every item. So I have made a belongs_to relationship with productItem. Every product item has its own specifications. Now I feel the necessity to make few common methods. From the concept of OOP, I have tried to make Parent class Sub class model, where subclass will inherit all the methods of parent. So I have tried doing following:
class Product < ActiveRecord::Base
def check
...
end
end
class ProductItem < Product
belongs_to :product
end
which raises following error:
undefined method `some_field_id' for #<ProductItem:0x007f8cac666850>
So how can I do parent subclass model in rails?
Class inheritance it's not for code sharing, you should rely on mixins for such a thing. Create a module like this, you can put it in app/shared/yourmodule.rb:
module Yourmodule
def dosomething
end
end
class Product
include Yourmodule
end
class ProductItem
include Yourmodule
end
Product.new.dosomething
ProductItem.new.dosomething
You can share attributes through STI though, but STI is recommended only if you share all parent attributes plus a few additional fields, be careful with it.
STI is simple: add (if you don't have it already) a type column to the table you want to inherit from, make it a string and don't set any default and leave it nullable. Now you can just do:
class Product < ActiveRecord::Base
end
class ProductItem < Product
end
You'll be able to access anything from Product
So here is the scenario. Lets say that you have 10 different suppliers that provide products that you sell. Now for this scenario assume that each supplier has their own methods for order processing and each require their own class. This is a big assumption, but I am trying to simplify my real scenario without all of the details.
Solution with Single Table Inheritance (STI)
class Supplier < ActiveRecord::Base
has_many :products
# Methods that can be inherited by each supplier go here
end
class MySupplier1 < Supplier
# Custom per-supplier methods go here
end
class MySupplier2 < Supplier
# Custom per-supplier methods go here
end
What is strange about this solution is that you will have 10 rows in your suppliers table. For example, you will never have more than one instance of MySupplier1.
Here is another solution:
Alternative Modular Solution
class Supplier < ActiveRecord::Base
has_many :products
end
class Suppliers::Base
# Methods that can be inherited by each supplier go here
end
class Suppliers::MySupplier1 < Suppliers::Base
# Custom per-supplier methods go here
end
class Suppliers::MySupplier2 < Suppliers::Base
# Custom per-supplier methods go here
end
With this solution, I like that it feels more modular, but the relationship between the ActiveRecord Supplier and the Suppliers::MySupplier1 is really awkward. Basically, I have to store the supplier class in the suppliers table so I know what class to instantiate for processing.
Does anyone think there is a right or wrong or maybe even a better approach?
I have a situation where I want to make 'parametric' models in rails; for example I'd like to define PrototypeRecipe, and then be able to make multiple DerivedRecipe's; maybe one derived recipe uses more sugar and another uses less eggs or something. The critical point is that I want all the 'derived' instances to inherit properties from a single shared PrototypeRecipe, but be able to make local modifications.
Ideally, I'd like to be able to define methods on the prototype (say, putting together a shopping list), and have these methods respond to local changes in derived instances (so if I specified 3 eggs instead of 2, i could call the prototype's make_shopping_list function and it would reflect that).
Is there an existing method for accomplishing something like this? Here's the best I can come up with so far:
class Ingredient << ActiveRecord::Base
belongs_to :recipe, :polymorphic => true
# uuid => UUID String (for grouping ingredients which change between prototype and derived instances)
end
class PrototypeRecipe << ActiveRecord::Base
has_many :ingredients
def make_ingredient_list(derived_recipe = nil)
self.ingredients.map {|i| derived_recipe.nil? ? i : derived_recipe.ingredients.where(:ingredient_uuid => i.uuid).first }
end
end
class DerivedRecipe << ActiveRecord::Base
belongs_to :prototype_recipe
has_many :ingredients
def method_missing(sym, *args)
self.prototype_recipe.send( sym, *args, self)
end
end
I know this code can be made a lot cleaner, I'm more wondering if the general approach can be improved on. The basic idea is that ingredients would each have a unique ID. To modify a prototype recipe, you simply create an instance of DerivedRecipe, link it to the prototype, and then add an ingredient with the same UUID as one of the prototype's ingredients.
I'm not 100% on what behavior you are looking to have, so here's my attempted solution.
Single-Table Inheritance (STI). Your base class will be PrototypeRecipe and your child class will be DerivedRecipe.
In your prototype_recipes table, specify a type column (text). This signals to Rails you want to use STI. If you put your make_ingredients_list method inside the base class, it will be accessible from your child classes.
# app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
belongs_to :recipe, :class_name => "PrototypeRecipe"
...
end
# app/models/prototype_recipe.rb
class PrototypeRecipe < ActiveRecord::Base
has_many :ingredients
has_many :derived_recipes
def make_ingredient_list
...
end
end
# app/models/derived_recipe.rb
class DerivedRecipe < PrototypeRecipe
belongs_to :prototype_recipe
end
Now you can do something like:
#cupcakes = PrototypeRecipe.create
#cupcakes_with_extra_eggs = #cupcakes.derived_recipes.create
print #cupcakes_with_extra_eggs.make_ingredient_list
Is this what you were looking for?