Rails: функции макро-стиля

В моделях и контроллерах мы часто используем макросы Rails, такие как before_validation, skip_before_filter поверх определения класса.

Как это реализовано? Как добавить пользовательские?

Спасибо!

Ответы

Ответ 1

Это обычные функции Ruby. Рубиновый гибкий подход к синтаксису делает его лучше, чем есть. Вы можете создать свой собственный, просто написав свой метод как обычную функцию Ruby и выполнив одно из следующих действий:

  • помещая его где-нибудь, доступным для ваших контроллеров, таких как application.rb

  • помещая его в файл и требуя его.

  • смешивание кода в класс с помощью ключевого слова Ruby include.


Этот последний вариант отлично подходит для классов моделей, и первый вариант действительно предназначен только для контроллеров.


Пример


Пример первого подхода показан ниже. В этом примере мы добавляем код в класс ApplicationController (в application.rb) и используем его в других контроллерах.

class BusinessEntitiesController < ApplicationController

    nested_within :Glossary

    private

        #  Standard controller code here ....

Вложенная_within предоставляет вспомогательные функции и переменные, которые помогают идентифицировать идентификатор "родительского" ресурса. По сути, он анализирует URL на лету и доступен каждому из наших контроллеров. Например, когда запрос поступает в контроллер, он автоматически анализируется, и атрибут class @parent_resource устанавливается на результат поиска Rails. Побочным эффектом является то, что ответ "Not Found" отправляется обратно, если родительский ресурс не существует. Это избавляет нас от ввода кода плиты котла во всех вложенных ресурсах.

Это все звучит довольно умно, но это просто стандартная функция Ruby в глубине души...


    def self.nested_within(resource)
        #
        #   Add a filter to the about-to-be-created method find_parent_ud
        #
        before_filter :find_parent_id

        #
        #   Work out what the names of things
        #
        resource_name = "#{resource.to_s.tableize.singularize}"
        resource_id = "#{resource_name}_id"
        resource_path = "#{resource.to_s.tableize}_path"

        #
        #   Get a reference to the find method in the model layer
        #
        finder = instance_eval("#{resource}.method :find_#{resource_name}")


        #
        #   Create a new method which gets executed by the before_filter above
        #
        define_method(:find_parent_id) do
            @parent_resource = finder.call(params[resource_id])

            head :status => :not_found, :location => resource_path 
                    unless @parent_resource
        end
    end


Функция nested_within определена в ApplicationController (контроллеры/application.rb) и поэтому автоматически втягивается.

Обратите внимание, что nested_within выполняется внутри тела класса контроллера. Это добавляет метод find_parent_id к контроллеру.


Резюме

Комбинация гибкого синтаксиса Ruby и конфигурации Rail over-configuration делает все это более мощным (или более прочным), чем на самом деле.

В следующий раз, когда вы найдете классный метод, просто придерживайтесь контрольной точки перед ней и прокладывайте ее через нее. Ahh с открытым исходным кодом!

Сообщите мне, могу ли я помочь дальше или если вы хотите, чтобы указатели на то, как работает этот код вложенного_within.

Крис

Ответ 2

Крис отвечает правильно. Но здесь, где вы хотите бросить свой код, чтобы написать свой собственный:

Самый простой способ добавления таких методов контроллера - определить его в ApplicationController:

class ApplicationController < ActionController::Base
  ...
  def self.acts_as_awesome
    do_awesome_things
  end
end

Затем вы можете получить доступ к нему от отдельных контроллеров, например:

class AwesomeController < ApplicationController
  acts_as_awesome
end

Для моделей вы хотите снова открыть ActiveRecord::Base:

module ActiveRecord
  class Base
    def self.acts_as_super_awesome
      do_more_awesome_stuff
    end
  end
end

Я лично поместил бы это в файл в config/initializers, чтобы он загружался один раз, и поэтому я знаю, где его всегда искать.

Затем вы можете получить доступ к нему в таких моделях:

class MySuperAwesomeModel < ActiveRecord::Base
  acts_as_super_awesome
end