Slide 1

Slide 1 text

Diary of a Mad Rails Engineer Akira Matsuda 2013

Slide 2

Slide 2 text

self.name Akira Matsuda (দా ໌)

Slide 3

Slide 3 text

Matz? ɹ matz: Yukihiro Matsumoto (দຊ ߦ߂)

Slide 4

Slide 4 text

Matz! me: Akira Matsuda (দా ໌) দ (= matz) matz: Yukihiro Matsumoto (দຊ ߦ߂)

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Rails Engineer?

Slide 7

Slide 7 text

https://twitter.com/kosaki55tea/ status/147525384546689024 Boeing 787 engine engineer strongly objects to software engineers calling them selves engineers.

Slide 8

Slide 8 text

Ruby on Rails has Engines!

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

Engine.is_a Rails plugin plugin.is_a Gem

Slide 11

Slide 11 text

Minimum Rails plugin % cd RAILS_ROOT % mkdir lsrc lsrc/lsrc.gemspec

Slide 12

Slide 12 text

The gemspec # lsrc/lsrc.gemspec Gem::Specification.new do |g| g.name = 'lsrc' g.version = '0' end

Slide 13

Slide 13 text

Gem le # RAILS_ROOT/Gemfile gem 'lsrc', path: 'lsrc'

Slide 14

Slide 14 text

Bundler (RubyGems) adds PLUGIN_ROOT/lib into $LOAD_PATH % rails r 'puts $:'

Slide 15

Slide 15 text

Say hello from our plugin # lsrc/lib/lsrc.rb puts 'Hello, LSRC!'

Slide 16

Slide 16 text

What happens when bundling lsrc gem? RubyGems adds the lib directory into $LOAD_PATH Then someone calls lsrc/lib/ lsrc.rb le somehow

Slide 17

Slide 17 text

Let's see who called lsrc.rb # lsrc/lib/lsrc.rb puts caller puts 'Hello, LSRC!'

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

YOUR_APP::Application. initialize! YOUR_APP/con g/environment.rb => railties/lib/rails/application.rb: initialize! => railties/lib/rails/initalizable.rb: run_initializers => raities/lib/rails/application.rb: initializer ... => Railties.new

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Rails Engine

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Enginize lsrc gem by subclassing Rails::Engine # lsrc/lib/lsrc.rb module Lsrc class Engine < ::Rails::Engine end end

Slide 28

Slide 28 text

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 ...

Slide 29

Slide 29 text

Doesn't have to be a named class # lsrc/lib/lsrc.rb module Lsrc Class.new ::Rails::Engine end

Slide 30

Slide 30 text

Doesn't have to be wrapped inside the Lsrc module # lsrc/lib/lsrc.rb Class.new ::Rails::Engine

Slide 31

Slide 31 text

Engine can contain models # lsrc/app/models/foo.rb class Foo def foo p 'foooo' end end

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

$LOAD_PATH % rails r 'puts $:'

Slide 34

Slide 34 text

eager_load_paths? # lsrc/lib/lsrc.rb p Class.new(::Rails::Engine).config.eager_load_paths

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Rails Engine is_a plugin is_a Gem is_a tiny application that can contain models, controllers, routes, etc.

Slide 37

Slide 37 text

What can Engines do?

Slide 38

Slide 38 text

My First Rails Engine

Slide 39

Slide 39 text

amatsuda/kaminari A pagination plugin Created for Rails 3.0.0 AFAIK the 2nd Rails Engine in the world

Slide 40

Slide 40 text

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)

Slide 41

Slide 41 text

Anatomy of Kaminari kaminari !"" app # !"" helpers # $"" views # $"" kaminari !"" config # $"" locales !"" lib # !"" generators # # $"" kaminari # # $"" templates # $"" kaminari # !"" helpers # $"" models $"" spec

Slide 42

Slide 42 text

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!

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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/

Slide 46

Slide 46 text

"Kaminari" Naming a gem is so hard There was no available /.*page.*/ name @hsbt named it "kaminari" probably inspired by "nokogiri"

Slide 47

Slide 47 text

"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)

Slide 48

Slide 48 text

New version? The next version will be 0.15.0 Coming soon...

Slide 49

Slide 49 text

Mountable Engines

Slide 50

Slide 50 text

The Problem constants, routes, helpers often con ict between main_app and Engines

Slide 51

Slide 51 text

The Solution Since Rails 3.1 "Isolated Engine" or "Mountable Engine"

Slide 52

Slide 52 text

Mountable Engine % rails plugin new my_engine -- mountable

Slide 53

Slide 53 text

Mountable Engine's Engine # my_engine/lib/my_engine/ engine.rb module MyEngine class Engine < ::Rails::Engine isolate_namespace MyEngine end end

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Real world examples Documentations in Rails ER diagram Page transition diagram

Slide 56

Slide 56 text

ER Diagram

Slide 57

Slide 57 text

amatsuda/erd

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

(DEMO)

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

amatsuda/roundabout

Slide 63

Slide 63 text

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`

Slide 64

Slide 64 text

(DEMO) % rails g scaffold user name age:integer % rake db:migrate add spec/feature/???_spec.rb

Slide 65

Slide 65 text

What roundabout does Generates documents Tells us missing `visit`s Shows us "page visit coverage" Motivates users to complete feature specs

Slide 66

Slide 66 text

amatsuda/hocus_pocus

Slide 67

Slide 67 text

hocus_pocus Released before 3.1.0 stable AFAIK this was the rst released mountable Engine in the world

Slide 68

Slide 68 text

HocusPocus Records your browser action You can copy & paste the generated text into your spec/ features. No need to handwrite anymore!

Slide 69

Slide 69 text

(DEMO)

Slide 70

Slide 70 text

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`

Slide 71

Slide 71 text

(DEMO)

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Have fun with Rails Engines, and be a great Rails Engineer!