How to Do Sti and Still Use Polymorphic Path Helpers

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:

Sample Image

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 is Person even though the actual class is Vendor.

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. The class_name of the attachable is passed as a String.

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



Leave a reply



Submit