Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Diary of a Mad Rails Engineer

Diary of a Mad Rails Engineer

Slides for LoneStarRuby Conference 2013 talk: "Diary of a Mad Rails Engineer" http://www.lonestarruby.org/2013/lsrc

Akira Matsuda

July 20, 2013
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. self.inspect GitHub: amatsuda Twitter: @a_matsuda a committer of: Ruby, Rails,

    Haml the author of: kaminari, active_decorator, action_args, gem-src, html5_validators, kawaii_validation, i18n_generators, ... the founder of: Asakusa.rb an organizer of: RubyKaigi
  2. What are Rails Engines? Engine is_a Rails plugin ( +

    Rails app itself) "Engines can be considered miniature applications that provide functionality to their host applications" (Rails Guides)
  3. What happens when bundling lsrc gem? RubyGems adds the lib

    directory into $LOAD_PATH Then someone calls lsrc/lib/ lsrc.rb le somehow
  4. the callers were require require require require... # gems/bundler-1.3.5/lib/bundler/runtime.rb module

    Bundler class Runtime < Environment def require(*groups) ... Array(dep.autorequire || dep.name).each do |file| required_file = file Kernel.require file end ... ennnnnd
  5. Kernel.require(autorequire || name) I didn't set anything to "autorequire" attribute,

    so it just requires the "name" equivalent to `require 'lsrc'` lsrc/lib/ is included in $LOAD_PATH, so nally ruby nds lsrc/lib/lsrc.rb and loads it
  6. Adding Railtie class and an initializer # lsrc/lib/lsrc.rb module Lsrc

    class Railtle < ::Rails::Railtie initializer 'lsrc.init' do puts 'initializing lsrc...' end end end
  7. How this adds hooks? # railties/lib/rails/railtie.rb module Rails class Railtie

    class << self def inherited(base) unless base.abstract_railtie? subclasses << base end end ... ennnd
  8. Railtie.subclasses # railties/lib/rails/engine/railties.rb module Rails class Engine < Railtie class

    Railties include Enumerable attr_reader :_all def initialize @_all || = ::Rails::Railtie.subclasses.map(&:instance) + ::Rails::Engine.subclasses.map(&:instance) end ... ennnd
  9. Anatomy of Rails Engine my_engine !"" app # !"" assets

    # # !"" images # # !"" javascripts # # $"" stylesheets # !"" controllers # !"" helpers # !"" mailers # !"" models # $"" views !"" bin !"" config !"" lib # !"" my_engine # # !"" engine.rb # # $"" version.rb # !"" my_engine.rb # $"" tasks $"" test
  10. Difference between Engines and non-Engine plugins An Engine can contain

    models, controllers, views, routes, etc An Engine should contain a subclass of ::Rails::Engine
  11. What happens when subclassed? # railties/lib/rails/engine.rb class Engine < Railtie

    class << self attr_accessor :called_from, :isolated ... def inherited(base) ... base.called_from = begin call_stack = caller.map { |p| p.sub(/:\d+.*/, '') } File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] }) end ...
  12. Doesn't have to be wrapped inside the Lsrc module #

    lsrc/lib/lsrc.rb Class.new ::Rails::Engine
  13. How could Rails nd Foo? lsrc/app/models was added in $LOAD_PATH

    lsrc/app/models was added in the Engine's eager_load_paths
  14. Make it a webapp #lsrc/config/routes.rb Rails.application.routes.draw do resources :foos end

    # lsrc/app/controllers/foos_controller.rb class FoosController < ApplicationController def index render text: Foo.new.foo end end
  15. Rails Engine is_a plugin is_a Gem is_a tiny application that

    can contain models, controllers, routes, etc.
  16. Why Engine? Rails Engine can contain app/ {controllers,models,views,helpers,etc} A pagination

    library consists of model extension, helpers, and views It was quite natural to implement as an Engine (Just wanted to try the new feature of Rails 3.0)
  17. Anatomy of Kaminari kaminari !"" app # !"" helpers #

    $"" views # $"" kaminari !"" config # $"" locales !"" lib # !"" generators # # $"" kaminari # # $"" templates # $"" kaminari # !"" helpers # $"" models $"" spec
  18. Initial version https://github.com/amatsuda/ kaminari/tree/v0.1.0 There exist some bugs, but the

    code is pretty straightforward Only 4 les in kaminari/lib/ directory!
  19. v0.1.0 railtie.rb for the Raitie class engine.rb for the Engine

    class active_record.rb for AR extension helper.rb for the helpers (don't read) views are in app/views
  20. active_record.rb (edited) # kaminari/lib/kaminari/active_record.rb (edited) module Kaminari::ActiveRecord extend ActiveSupport::Concern included

    do def self.inherited(kls) kls.class_eval do scope :page, lambda {|num| offset(PER_PAGE * ([num.to_i, 1].max - 1)).limit(10) } do def per(num) offset(offset_value / limit_value * num).limit(num) end ... ennnnnd
  21. How this code works? How does Kaminari paginate? (Pat Shaughnessy)

    http://patshaughnessy.net/2011/9/10/ how-does-kaminari-paginate Active Record scopes vs class methods (Carlos Antônio) http://blog.plataformatec.com.br/ 2013/02/active-record-scopes-vs- class-methods/
  22. "Kaminari" Naming a gem is so hard There was no

    available /.*page.*/ name @hsbt named it "kaminari" probably inspired by "nokogiri"
  23. "Kaminari" ཕ == thunder I know it's hard to memorize,

    hard to spell, and hard to pronounce for you, non-Japanese In fact, everybody pronounces it incorrectly (I don't mind though)
  24. What can a Mountable Engine do? It can provide safely

    namespaced models, controllers, views, etc. It can be "mounted" onto the parent Rails application's certain URL It can refer to the parent application from controllers / views via `main_app` method
  25. ERD Developed during RailsConf 2012 Made in Austin A mounted

    Engine that loads the main_app's models and draws the ER diagram on the browser
  26. A tiny tip erd mounts itself onto the main_app's routes

    on the :after_initialize hook, so users don't have to con gure anything
  27. Self-mounting Engine # erd/lib/erd/railtie.rb module Erd class Railtie < ::Rails::Railtie

    #:nodoc: initializer 'erd' do |app| ActiveSupport.on_load(:after_initialize) do if Rails.env.development? Rails.application.routes.append do mount Erd::Engine, :at => '/erd' ennnnnnd
  28. I'll be the Roundabout Visualizes page (action) transitions No need

    for any extra programming or document writing. Just run `rake spec`. Records all page transitions in `rake spec`
  29. (DEMO) % rails g scaffold user name age:integer % rake

    db:migrate add spec/feature/???_spec.rb
  30. What roundabout does Generates documents Tells us missing `visit`s Shows

    us "page visit coverage" Motivates users to complete feature specs
  31. HocusPocus Records your browser action You can copy & paste

    the generated text into your spec/ features. No need to handwrite anymore!
  32. HocusPocus (reprise) A Wiki-wiki way Rails app development platform Users

    can edit current page like a Wiki page Catches URL missing in the browser for example, just visit http://localhost:3000/books Catches link_to missing in the browser for example, add `link_to 'authors', authors_path`
  33. Anatomy of HocusPocus hocus_pocus !"" engines # !"" command_line #

    # !"" app # # # !"" controllers # # # $"" views # # !"" config # # $"" lib # !"" editor # # !"" app # # # !"" assets # # # !"" controllers # # # $"" views # # !"" config # # $"" lib # !"" generator # # !"" app # # # !"" assets # # # !"" controllers # # # !"" helpers # # # $"" views # # !"" config # # $"" lib # $"" recorder # !"" app # # !"" assets # # !"" controllers # # $"" views # !"" config # $"" lib !"" lib # !"" generators # # $"" hocus_pocus # $"" hocus_pocus $"" spec
  34. Engines in an Engine! AFAIK the only Rails Engine that

    contains other Rails Engines inside Adds each Engine's lib, app/* into $LOAD_PATH require_paths does the trick
  35. Gem::Speci cation #require_path # hocus_pocus/hocus_pocus.gemspec Gem::Specification.new do |s| s.name =

    'hocus_pocus' s.version = HocusPocus::VERSION s.authors = ['Akira Matsuda'] s.homepage = 'https://github.com/amatsuda/hocus_pocus' ... s.require_paths = ['lib', 'engines/generator/lib', 'engines/ editor/lib', 'engines/recorder/lib', 'engines/command_line/lib'] ... end