My Rails views and controllers are littered with redirect_to
, link_to
, and form_for
method calls. Sometimes link_to
and <
This way works for me ok (define this method in the base class):
def self.inherited(child)
child.instance_eval do
alias :original_model_name :model_name
def model_name
Task::Base.model_name
end
end
super
end
Following @prathan-thananart answer, and for the multiple STI classes, you can add the following to the parent model ->
class Contact < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, 'Contact')
end
end
That will make each form with Contact data to send params as params[:contact]
instead of params[:contact_person]
, params[:contact_whatever]
.
I was having trouble with this problem too and came by this answer on a question similar to ours. It worked for me.
form_for @list.becomes(List)
Answer shown here: Using STI path with same controller
The .becomes
method is defined as mainly used for solving STI problems like your form_for
one.
.becomes
info here: http://apidock.com/rails/ActiveRecord/Base/becomes
Super late response, but this is the best answer I could find and it worked well for me. Hope this helps some one. Cheers!
You can create method that returns dummy Parent object for routing purpouse
class Person < ActiveRecord::Base
def routing_object
Person.new(id: id)
end
end
and then simply call form_for @employee.routing_object which without type will return Person class object
Ok, Ive had a ton of frustration in this area of Rails, and have arrived at the following approach, perhaps this will help others.
Firstly be aware that a number of solutions above and around the net suggest using constantize on client provided parameters. This is a known DoS attack vector as Ruby does not garbage collect symbols, thus allowing an attacker to create arbitrary symbols and consume available memory.
I've implemented the approach below which supports instantiation of model subclasses, and is SAFE from the contantize problem above. It is very similar to what rails 4 does, but also allows more than one level of subclassing (unlike Rails 4) and works in Rails 3.
# initializers/acts_as_castable.rb
module ActsAsCastable
extend ActiveSupport::Concern
module ClassMethods
def new_with_cast(*args, &block)
if (attrs = args.first).is_a?(Hash)
if klass = descendant_class_from_attrs(attrs)
return klass.new(*args, &block)
end
end
new_without_cast(*args, &block)
end
def descendant_class_from_attrs(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
return nil if subclass_name.blank? || subclass_name == self.name
unless subclass = descendants.detect { |sub| sub.name == subclass_name }
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
subclass
end
def acts_as_castable
class << self
alias_method_chain :new, :cast
end
end
end
end
ActiveRecord::Base.send(:include, ActsAsCastable)
After trying various approaches for the 'sublclass loading in devlopment issue' many similar to whats suggested above, I found the only thing that worked reliably was to use 'require_dependency' in my model classes. This ensures that class loading works properly in development and causes no issues in production. In development, without 'require_dependency' AR wont know about all subclasses, which impacts the SQL emitted for matching on the type column. In addition without 'require_dependency' you can also end up in a situation with multiple versions of the model classes at the same time! (eg. this can happen when you change a base or intermediate class, the sub-classes don't always seem to reload and are left subclassing from the old class)
# contact.rb
class Contact < ActiveRecord::Base
acts_as_castable
end
require_dependency 'person'
require_dependency 'organisation'
I also don't override model_name as suggested above because I use I18n and need different strings for the attributes of different subclasses, eg :tax_identifier becomes 'ABN' for Organisation, and 'TFN' for Person (in Australia).
I also use route mapping, as suggested above, setting the type:
resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }
In addition to the route mapping, I'm using InheritedResources and SimpleForm and I use the following generic form wrapper for new actions:
simple_form_for resource, as: resource_request_name, url: collection_url,
html: { class: controller_name, multipart: true }
... and for edit actions:
simple_form_for resource, as: resource_request_name, url: resource_url,
html: { class: controller_name, multipart: true }
And to make this work, in my base ResourceContoller I expose InheritedResource's resource_request_name as a helper method for the view:
helper_method :resource_request_name
If you're not using InheritedResources, then use something like the following in your 'ResourceController':
# controllers/resource_controller.rb
class ResourceController < ApplicationController
protected
helper_method :resource
helper_method :resource_url
helper_method :collection_url
helper_method :resource_request_name
def resource
@model
end
def resource_url
polymorphic_path(@model)
end
def collection_url
polymorphic_path(Model)
end
def resource_request_name
ActiveModel::Naming.param_key(Model)
end
end
Always happy to hear others experiences and improvements.
Here is a safe clean way to have it work in forms and throughout your application that we use.
resources :districts
resources :district_counties, controller: 'districts', type: 'County'
resources :district_cities, controller: 'districts', type: 'City'
Then I have in my form. The added piece for this is the as: :district.
= form_for(@district, as: :district, html: { class: "form-horizontal", role: "form" }) do |f|
Hope this helps.