Лучшие практики для обработки маршрутов для подклассов STI в рельсах - PullRequest
168 голосов
/ 22 декабря 2010

Представления и контроллеры My Rails заполнены вызовами методов redirect_to, link_to и form_for. Иногда link_to и redirect_to явно указаны в путях, которые они связывают (например, link_to 'New Person', new_person_path), но во многих случаях пути являются неявными (например, link_to 'Show', person).

Я добавляю в мою модель некоторое наследование отдельных таблиц (STI) (скажем, Employee < Person), и все эти методы ломаются для экземпляра подкласса (скажем, Employee); когда rails выполняет link_to @person, он ошибается с undefined method employee_path' for #<#<Class:0x000001022bcd40>:0x0000010226d038>. Rails ищет маршрут, определенный именем класса объекта-сотрудника. Эти маршруты сотрудников не определены, и нет контроллера сотрудников, поэтому действия также не определены.

Этот вопрос задавался ранее:

  1. На StackOverflow ответ заключается в том, чтобы отредактировать каждый экземпляр link_to и т. Д. Во всей кодовой базе и указать путь в явном виде
  2. При StackOverflow снова два человека предлагают использовать routes.rb для сопоставления ресурсов подкласса с родительским классом (map.resources :employees, :controller => 'people'). Верхний ответ в том же вопросе SO предлагает приведение типов каждого объекта экземпляра в кодовой базе с использованием .becomes
  3. Еще один в StackOverflow , лучший ответ - в лагере Do Repeat Yourself, и предлагает создать дублированные леса для каждого подкласса.
  4. Вот тот же вопрос снова в SO, где верхний ответ кажется просто неправильным (Rails magic Just Works!)
  5. В другом месте в Интернете я нашел это сообщение в блоге , где F2Andy рекомендует редактировать путь в любом месте кода.
  6. В блоге Одиночное наследование таблиц и маршруты RESTful в Logical Reality Design рекомендуется сопоставлять ресурсы для подкласса с контроллером суперкласса, как в ответе SO номер 2 выше.
  7. У Алекса Рейснера есть пост Наследование отдельных таблиц в Rails , в котором он выступает против сопоставления ресурсов дочерних классов родительскому классу в routes.rb, так как он только перехватывает разрывы маршрутизации от link_to и redirect_to, но не от form_for. Поэтому он рекомендует вместо этого добавить метод в родительский класс, чтобы подклассы лгали об их классе. Звучит хорошо, но его метод дал мне ошибку undefined local variable or method `child' for #.

Таким образом, ответ, который кажется наиболее элегантным и имеет наибольшее согласие (но это еще не все , что элегантно, или , что много консенсуса) - это добавление ресурсов к вашему routes.rb. За исключением того, что это не работает для form_for. Мне нужна ясность! Чтобы выбрать варианты выше, мои варианты

  1. сопоставить ресурсы подкласса с контроллером суперкласса в routes.rb (и надеюсь, мне не нужно вызывать form_for для каких-либо подклассов)
  2. Переопределить внутренние методы rails, чтобы классы лгали друг другу
  3. Редактировать каждый экземпляр в коде, где путь к действию объекта вызывается неявно или явно, либо изменяя путь, либо приводя тип к объекту.

Со всеми этими противоречивыми ответами мне нужно решение. Мне кажется, что нет хорошего ответа. Это провал в дизайне рельсов? Если это так, это ошибка, которая может быть исправлена? Или, если нет, то я надеюсь, что кто-то может объяснить мне это, показать мне плюсы и минусы каждого варианта (или объяснить, почему это не вариант), какой из них правильный и почему. Или есть правильный ответ, которого я не нахожу в Интернете?

Ответы [ 15 ]

1 голос
/ 26 августа 2016

Вы можете создать метод, который возвращает фиктивный родительский объект для маршрутизации purpouse

class Person < ActiveRecord::Base      
  def routing_object
    Person.new(id: id)
  end
end

, а затем просто вызовите form_for @ employee.routing_object, который без типа вернет объект класса Person

1 голос
/ 21 мая 2013

Этот способ работает для меня нормально (определите этот метод в базовом классе):

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
1 голос
/ 27 апреля 2013

Если я рассматриваю наследование ИППП следующим образом:

class AModel < ActiveRecord::Base ; end
class BModel < AModel ; end
class CModel < AModel ; end
class DModel < AModel ; end
class EModel < AModel ; end

в 'app / models / a_model.rb' добавляю:

module ManagedAtAModelLevel
  def model_name
    AModel.model_name
  end
end

А затем в классе AModel:

class AModel < ActiveRecord::Base
  def self.instanciate_STI
    managed_deps = { 
      :b_model => true,
      :c_model => true,
      :d_model => true,
      :e_model => true
    }
    managed_deps.each do |dep, managed|
      require_dependency dep.to_s
      klass = dep.to_s.camelize.constantize
      # Inject behavior to be managed at AModel level for classes I chose
      klass.send(:extend, ManagedAtAModelLevel) if managed
    end
  end

  instanciate_STI
end

Поэтому я даже могу легко выбрать, какую модель я хочу использовать по умолчанию, и даже не касаясь определения подкласса. Очень сухой.

0 голосов
/ 04 апреля 2019

После ответа @ prathan-thananart для нескольких классов STI можно добавить следующее в родительскую модель ->

class Contact < ActiveRecord::Base
  def self.model_name
    ActiveModel::Name.new(self, nil, 'Contact')
  end
end

Это позволит каждой форме с контактными данными отправлять параметры как params[:contact] вместо params[:contact_person], params[:contact_whatever].

0 голосов
/ 23 января 2012

хакерский, но просто еще один в списке решений.

class Parent < ActiveRecord::Base; end

Class Child < Parent
  def class
    Parent
  end
end

работает на рельсах 2.x и 3.x

...