Rails custom Scaffolding


    Наверное каждый в начале работы с ROR был впечатлен возможностью Scaffolding'а, который позволяет одной командой создавать migrations, controllers, models и views.

    Но что делать если в своем проекте вы используете не стандартные Rails утилиты: erb, Test::Unit, fixturies, а сторонние инструменты: Haml, Rspec, Cucumber, Factory Girl и более того, хотите добавить собственные шаблоны?

    Интересно? GOTO next line.

    Исходные данные:
    Ruby on Rails; Rspec; will-paginate; Haml; Factory-girl

    Задача:
    % rails generate scaffold post post:string
    Генерирует:
    1. contoller's с поддержкой will_paginate, русскоязычными сообщениями
    2. model's с поддержкой will_paginate
    3. view's с заданным нами содержимым и в формате haml
    4. Rspec тесты, вместо Test::Unit
    5. Factory Girl factories вместо стандартных fixtures



    Для начала коротко о Scaffold. Scaffold — это встроенный генератор, сам по себе он ничего не генерирует, но запускает другие генераторы.

    Текущие настройки scaffold можно проверить с помощью:
    % rails g scaffold --help

    Генераторы настраиваются с помощью файла RAILS_ROOT/config/application.rb, внутри которого можно задавать параметры с помощью такой конструкции:
    config.generators do |g|
      # настройки генераторов
    end

    CUSTOM CONTROLLERS


    Начнём с того, что заменим стандартный шаблон контроллеров на свой.
    Необходимо скопировать шаблон из гема используемого в проекте в сам проект.
    Путь до гемов вашего проекта можно узнать таким способом:
    RAILS_ROOT% rails console
    puts $LOAD_PATH
    ...
    ...
    ... тут следует список всех гемов с полными путями


    Скопируем шаблон controller.rb из GEMS_PATH/railties[version]/lib/rails/generators/rails/scaffold_controller/templates в RAILS_ROOT/lib/templates/rails/scaffold_controller/controller.rb

    #Вот так выглядит исходный шаблон контроллера controller.rb
    class <%= controller_class_name %>Controller < ApplicationController
      # GET <%= route_url %>
      # GET <%= route_url %>.xml
      def index
        @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
    
        respond_to do |format|
          format.html # index.html.erb
          format.xml  { render :xml => @<%= plural_table_name %> }
        end
      end
    ...

    Немного кастомиризуем его, добавив поддержку русского языка, паджинацию и обрезав лишний текст
    # encoding: UTF-8
    class <%= controller_class_name %>Controller < ApplicationController
      # GET <%= route_url %>
      def index
        @<%= plural_table_name %> = <%= class_name %>.paginate :page => params[:page], :order => 'id DESC'
      end
    ...

    После работы scaffold мы получим такой миловидный код
    # encoding: UTF-8
    class PostsController < ApplicationController
      # GET /posts
      def index
        @posts = Post.paginate :page => params[:page], :order => 'id DESC'
      end
    ...

    CUSTOM MODELS


    Скопируем файл шаблона model.rb с GEMS_PATH/activerecord[version]/lib/rails/generators/active_record/model/templates в RAILS_ROOT/lib/templates/active_record/model/model.rb

    #Исходный код шаблона model.rb
    class <%= class_name %> < <%= parent_class_name.classify %>
    <% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
      belongs_to :<%= attribute.name %>
    <% end -%>
    end


    Добавим поддержку русского языка и 2 параметра для will_paginate
    # encoding: UTF-8
    class <%= class_name %> < <%= parent_class_name.classify %>
      cattr_reader :per_page
      @@per_page = 20
    <% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
      belongs_to :<%= attribute.name %>
    <% end -%>
    end


    На выходе получаем:
    # encoding: UTF-8
    class Post < ActiveRecord::Base
      cattr_reader :per_page
      @@per_page = 20
    end
    

    CUSTOM VIEWS


    В нашем примере мы используем HAML вместо .erb. Как заставить Rails генерировать не erb, a haml view's?
    Очень просто, достаточно установить gem «haml-rails».
    Дописываем в Gemfile
    gem 'haml-rails'

    И запускаем bundle install

    Готово! Теперь рельсогенератор делает file_name.html.haml файлы для нас

    Перейдём к нашим шаблонам. Скопируем 5 файлов (edit.html.haml, _form.html.haml, index.html.haml, new.html.haml, show.html.haml) из GEMS_PATH/haml-rails[version]/lib/generators/haml/scaffold/templates в уже полюбившуюся нам RAILS_ROOT/lib/templates/haml/scaffold

    Не буду приводить здесь кода, его можно посмотреть по ссылкам ниже

    Исходные шаблоны — haml-rails
    Шаблон после редактирования — ссылка
    Результат — ссылка

    RSPEC


    Как и в случае с haml, для замены стандартных тестов RSpec'овскими достаточно установить gem «rspec-rails».

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

    Наверняка вам как и мне не нужно столько rspec файлов, к счастью ROR позволяет нам настроить этот пункт.

    #RAILS_ROOT/config/application.rb
    config.generators do |g|
      #Здесь я отключил генерацию rspec файлов для вьюх, хелперов, роутинга и запросов
      #т.о. оставив лишь генерацию spec's для моделей и контроллеров
      g.test_framework :rspec, :view_specs => false, :helper_specs => false, 
                               :routing_specs => false, :request_specs => false
    end

    FACTORY GIRL


    Для добавления scaffolding для FG, установим gem «rails3-generators»
    И добавим в конфиг следующую строчку
    #RAILS_ROOT/config/application.rb
    config.generators do |g|
        g.test_framework :rspec, :view_specs => false, :helper_specs => false, 
                                 :routing_specs => false, :request_specs => false
        g.fixture_replacement :factory_girl, :dir => "spec/factories"
    end

    Помимо factory_girls гем «rails3-generators» добавляет генераторы для DataMapper, Authlogic, Mongomapper, Shoulda, Formtastic и SimpleForm

    Что же у нас получилось?


    % rails g scaffold final final:string
    invoke  active_record
          create    db/migrate/20110713193843_create_finals.rb
          create    app/models/final.rb
          invoke    rspec
          create      spec/models/final_spec.rb
          invoke      factory_girl
          create        spec/factories/finals.rb
           route  resources :finals
          invoke  scaffold_controller
          create    app/controllers/finals_controller.rb
          invoke    haml
          create      app/views/finals
          create      app/views/finals/index.html.haml
          create      app/views/finals/edit.html.haml
          create      app/views/finals/show.html.haml
          create      app/views/finals/new.html.haml
          create      app/views/finals/_form.html.haml
          invoke    rspec
          create      spec/controllers/finals_controller_spec.rb
          invoke      helper
          invoke      rspec
          invoke    helper
          create      app/helpers/finals_helper.rb
          invoke      rspec
          invoke  stylesheets
       identical    public/stylesheets/scaffold.css

    Собственные шаблоны для MVC; haml view's; factory_girl factories; RSpec's для моделей и контроллеров; встроенная паджинация.

    Всё как мы хотели. Наслаждайтесь))

    Посмотреть полный код

    Github

    Почитать

    Generators manual
    Paul's Barry Article: Customizing generators in rails 3
    Rspec generators detail
    Подкаст про генераторы
    rails3-generators gem
    haml-rails gem
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 26
    • 0
      Мне наоборот при знакомстве с Rails показалось, что скаффолдинг там как то не совсем к месту… Скорее это замануха для начинающих, которую они используют для создания первого блога, типа смотрите как быстро и круто.

      Впрочем, кто-то из создателей rails писал, что, в общем то, так и есть, и для настоящей разработки скаффолдинг, скорее всего, не потребуется.

      Тем более идея скаффолдинга несколько противоречит TDD, как бы его под себя не настраивали.

      А в целом, статья про кустомные генераторы познавательна, спасибо!
      • +1
        Скаффолдинг может оказаться полезным когда вы создаёте много однотипных страниц, например при написании админки к большому информационному порталу.
        Что касается TDD — это хорошая практика использовать генераторы при написании кода. Например вы пишете тест, потом генерируете необходимые файлы, немного кастомизируете и вуаля.
        Пример того как это выглядит в жизни, можно посмотреть здесь railscasts.com/episodes/155-beginning-with-cucumber.
        • 0
          Когда пишется админка, модели и тесты для них, как правило, уже имеются, скаффолдинг тут каким боком?

          Кроме того, скаффолдинг плохо решает такие вещи, как вложенные ресурсы, неймспейсы, сингл ресурсы и тп.

          Мне ближе подход, когда я генерирую минимальный функционал и дописываю его, чем допиливаю скаффолд.
          • +2
            >> Когда пишется админка, модели и тесты для них, как правило, уже имеются, скаффолдинг тут каким боком?
            Пример из жизни — я разрабатываю финансовую программу в которой много однотипных справочников: сотрудники; должности; автомобили; модели автомобилей; типы кузовов автомобилей и т.п. Идея скаффолдинга отлично вписывается в этот проект.

            >> Мне ближе подход, когда я генерирую минимальный функционал и дописываю его, чем допиливаю скаффолд.
            Тоже отличный подход, оправданный на многих проектах. В этом случае на помощь приходят кастомные генераторы.
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                а я бы не стал его советовать. только для быстрого прототипирования.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • +1
                    сделал на нем админку для сайта. ужасно жалею. поддерживать и развивать — сплошной мат. dryml компилируется вечность, полное отсутствие сообщений об ошибках. разобраться что и почему не помагают даже исходники, так как там абстракция на абстракции. вот сделали вы страницу с формой — а форма не отображается. почему — а хер его… оно ни в логах ни где-то еще об этом не скажет. нафиг-нафиг. сейчас есть огромное количество — гемов-помощников, проще и лучше все сделать без подобных автоматизаторов всего и вся одной строчкой кода.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • +1
                        devise, declarative_authorization, formtastic, search_logic, compass…
                        • +1
                          Чуть иначе: devise, cancan, meta_search, compass, kaminari
                          За formtastic спасибо, не знал.
                          • 0
                            Можете что-нибудь сказать о saas?
                            • 0
                              *sass
                              • +1
                                Отличный язык, пользую. Правда, не sass, а scss. Можно петь много дифирамб, как и haml`у, но лучше просто дать ссылку на оффсайт — http://sass-lang.com/
                                • 0
                                  В последнем проекте использовал haml и словил оргазм. На сайте haml ссылка на scss, я посмотрел и скоро перепишу свой css с его помощью.

                                  А вопрос возник потому что вы написали про compass, как я понял из беглого прочтения Readme на github, эта штука тоже работает с CSS. В чем отличие sass-lang.com/ от compass?
                                  • 0
                                    В том, что compas — это набор хелперов для, собственно, sass.
          • +1
            Очень удобны скафолды когда нужно что-то проверить — в две команды генерируется готовое приложение и скаффолд, что дает, фактически, готовое приложение-песочницу.
          • +1
            Спасибо, интересная статья. Еще посоветовал бы посмотреть на гитхабе nifty-generators от Райана Бейтса, тоже отличный пример на эту тему в плане отправной точки к написанию своих генераторов.
            • 0
              Спасибо за совет, форкнул. Посмотрю как будет время
            • 0
              Задание параметра per_page в модели как-то омерзительно, тем более что его можно указать при метода вызове will_paginate (т.е. в контроллере). Не понимаю, почему сам автор гема так делает в примере на гитхабе.
              • 0
                Вероятно логика такова: раз этот параметр используется для построения запросов к базе, то и место ему в модели.
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  И вам спасибо за отзыв.
                  Нет не доводилось, можете описать его суть в двух словах?
                • +3
                  Не пользуйтесь will paginate. Он фактически мертв, плохо поддерживается и так и не был переписан для нормальной поддержки скоупов. Используйте kaminari…
                • +2
                  руби достаточно динамический язык, чтобы использовать абстракции вместо кодогенерации. зачем в него тянут худшие практики из статических языков? вам нужно 100500 страничек в админке? ну так подметьте, что между ними общего. наследование, агрегация, свойства — всё это позволит не повторять себя и не плодить копипасту.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое