Can I do STI and still use polymorphic path helpers?
Yep, just use AR::Base#becomes
.
Say your base class is Account
, which is subclassed to GuestAccount
and LoginAccount
.
@account.is_a? LoginAccount? #=> true
Then you can just do a
form_for [@account.becomes(Account), @comment] do |f|
...
How do you handle single table inheritance in SimpleForm so a single helper handles all models?
Specify :url=> networking_event_session_path
in the simple_form_for
.
Something like this:
<%= simple_form_form @session, :url => networking_event_session_path %>
Has many through, self related, and polymorphic for STI models saving STI parent class name instead of STI child class name
This is something known as STI
base class
, whereby Rails will save the Parent
class, even though you've called the subclass
. We have experience with it:
The above should have Node
as Company
...
I spent some time last year looking over this; there's a gem
called store_sti_base_class
:
Notice that
addressable_type
column isPerson
even though the actual class isVendor
.Normally, this isn't a problem, however it can have negative performance characteristic in certain circumstances. The most obvious one is that a join with persons or an extra query is required to find out the actual type of addressable.
The way around it - at least from the perspective of the gem
- is to extend ActiveRecord
so that it will save the actual class of the model, not its parent.
The only code I have - which is out of date - is as follows:
#config/initializers/sti_base.rb
ActiveRecord::Base.store_base_sti_class = false
#lib/extensions/sti_base_class.rb
module Extensions
module STIBaseClass #-> http://stackoverflow.com/questions/2328984/rails-extending-activerecordbase
extend ActiveSupport::Concern
included do
class_attribute :store_base_sti_class
self.store_base_sti_class = true
end
end
# include the extension
ActiveRecord::Base.send(:include, STIBaseClass)
####
module AddPolymorphic
extend ActiveSupport::Concern
included do #-> http://stackoverflow.com/questions/28214874/overriding-methods-in-an-activesupportconcern-module-which-are-defined-by-a-cl
define_method :replace_keys do |record=nil|
super(record)
owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name
Rails.logger.info record.class.base_class.name
end
end
end
ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, AddPolymorphic)
end
As I need to get this working, it might be worth bantering something around.
Why polymorphic association doesn't work for STI if type column of the polymorphic association doesn't point to the base model of STI?
Good question. I had exactly the same problem using Rails 3.1. Looks like you can not do this, because it does not work. Probably it is an intended behavior. Apparently, using polymorphic associations in combination with Single Table Inheritance (STI) in Rails is a bit complicated.
The current Rails documentation for Rails 3.2 gives this advice for combining polymorphic associations and STI:
Using polymorphic associations in combination with single table
inheritance (STI) is a little tricky. In order for the associations to
work as expected, ensure that you store the base model for the STI
models in the type column of the polymorphic association.
In your case the base model would be "Staff", i.e. "borrowable_type" should be "Staff" for all items, not "Guard". It is possible to make the derived class appear as the base class by using "becomes" : guard.becomes(Staff)
. One could set the column "borrowable_type" directly to the base class "Staff", or as the Rails Documentation suggests, convert it automatically using
class Car < ActiveRecord::Base
..
def borrowable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
STI polymorphic has_many uses wrong type value
From the Rails docs:
Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.
Note: The
attachable_type= method
is being called when assigning an
attachable. Theclass_name
of the attachable is passed as aString
.class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(class_name)
super(class_name.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets,as: :attachable, dependent: :destroy
end
class GuestPost < Post end
class MemberPost < Post end
Source: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
So it says that instead of storing the imageable_type = OneProduct
you need to store it as Product
only and you can add a type
column in the Product
table. But that depends completely on what you need from the OneProduct
model, if a default_scope
on that model will make it work for you without adding the type
column then don't add it on the Product
table and if that doesn't work then you can add the column and then add the default_scope
to fetch products.type = OneProduct
when querying on OneProduct
model.
Rails STI vs Polymorphic vs Neither
STI can work for this, but since the difference between the types of users is basically a flag that says a user is an admin, you just need something on a user to check whether they are allowed certain actions. Roles can be either exclusive or inclusive (so that a user can be both a Player and a Manager), but once set up you can shape the interface around the capabilities a user has:
<% if @user.has_role?('manager') %>
<%= render 'manager_fields' # or manager_page, etc. %>
<% end %>
How you handle this in the controller depends on how you implement the roles, either via gems like Pundit, CanCanCan, etc. or through explicit checks in something you write yourself. The gems will give you helper methods to reduce the amount of typing involved in views and a more declarative syntax on the back end.
Related Topics
Breaking Ruby Module Across Several Files
Refactoring Activerecord Models with a Base Class Versus a Base Module
How to Detect the End of a Method Chain in Ruby
Ruby, Tor and Net::Http::Proxy
Why Doesn't Module.Method_Defined(:Method) Work Correctly
How Rails Delegate Method Works
Including a Virtual Attribute in the Respond_With Hash
How to Do Named Capture in Ruby
How to Embed Ruby in JavaScript (Rails + .Html.Erb File)
Appending Headers to Rspec Controller Tests
Retrieving Image Height with Carrierwave
Is Assignment in a Conditional Clause Good Ruby Style
Ruby on Rails - Paperclip and Dynamic Parameters
Safe Navigation Equivalent to Rails Try for Hashes