Rails Engines from the bottom up

Slides for RubyShift 2013 talk "Rails Engines from the bottom up" http://rubyshift.org/すべて表示

Slides for RubyShift 2013 talk "Rails Engines from the bottom up" http://rubyshift.org/

省略して表示

10ヶ月前 にアップロード

(2013年09月28日)

スポンサーリンク

「Rails Engines from the bottom up」の内容

  1. Rails Engines from the bottom up Akira Matsuda
  2. self.name
  3. From Japan
  4. self.inspect
  5. Unknown Ruby 2.1 "new feature" # before 2.1: ActiveRecord::Base.send :include, MyPagination # since 2.1: ActiveRecord::Base.include MyPagination
  6. My least favorite Ruby 2.1 new feature # freezing String 'Kiev'.frozen # 'frozen String' literal (the f suffix) 'Kiev'f # "f" is not for "Float" '1.0'f # "to_f" to get a "Float" '1.0'f.to_f
  7. f to every Strings!  # f to every Strings! # Then this is how our code would look like... source 'https://rubygems.org'f gem 'rails'f, '4.0.0'f gem 'sqlite3'f gem 'sass-rails'f, '~> 4.0.0'f gem 'uglifier'f, '>= 1.3.0'f gem 'coffee-rails'f, '~> 4.0.0'f gem 'jquery-rails'f gem 'turbolinks'f gem 'jbuilder'f, '~> 1.2'f group :doc do gem 'sdoc'f, require: false end
  8. amatsuda
  9. Rails Engineer?
  10. Ruby on Rails has Engines!
  11. What are Rails Engines?
  12. Engine.is_a?
  13. From the bottom up
  14. Creating a gem
  15. We need a gemspec # hello/hello.gemspec Gem::Specification.new do |g| g.name = 'hello'f g.version = '0'f end
  16. Bundling via bundler # RAILS_ROOT/Gemfile gem 'hello'f, path: 'hello'f
  17. Bundler (RubyGems) adds PLUGIN_ROOT/lib into $LOAD_PATH
  18. Now let's implement some features
  19. Say hello from our plugin # hello/lib/hello.rb puts 'Hello, Kiev!'f
  20. What happens when bundling hello gem?
  21. Let's see who called hello.rb # hello/lib/hello.rb puts caller puts 'Hello, Kiev!'f
  22. the callers were require require require require... .../gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `require' .../gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `block (2 levels) in require' .../gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `each' .../gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `block in require' .../gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `each' .../gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `require' .../gems/bundler-1.3.5/lib/bundler.rb:132:in `require' ./config/application.rb:7:in `<top (required)>' .../gems/railties-4.0.0/lib/rails/commands/runner.rb:42:in `require' .../gems/railties-4.0.0/lib/rails/commands/runner.rb:42:in `<top (required)>' .../gems/railties-4.0.0/lib/rails/commands.rb:86:in `require' .../gems/railties-4.0.0/lib/rails/commands.rb:86:in `<top (required)>' ./bin/rails:4:in `require' ./bin/rails:4:in `<main>'
  23. How Bundler requires a gem # gems/bundler-1.3.5/lib/bundler/runtime.rb module Bundler class Runtime < Environment def require(*groups) ... @definition.dependencies.each do |dep| ... Array(dep.autorequire || dep.name).each do |file| required_file = file Kernel.require file end ... ennnnnd
  24. Kernel.require(autorequire || name)
  25. Creating a Rails plugin gem
  26. Adding Railtie class and an initializer # hello/lib/hello.rb module Hello class Railtle < ::Rails::Railtie initializer 'hello.init'f do puts 'initializing hello...'f end end end
  27. What happens when Raitie.inherited? # railties/lib/rails/railtie.rb module Rails class Railtie class << self def inherited(base) unless base.abstract_railtie? subclasses << base end end ... ennnd
  28. YOUR_APP::Application. initialize!
  29. 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
  30. The points so far
  31. Rails Engine
  32. 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
  33. Difference between Engines and non-Engine plugins
  34. Enginize hello gem by subclassing Rails::Engine # hello/lib/hello.rb module Hello class Engine < ::Rails::Engine end end
  35. If a class.is_a? Engine, it should be a Railtie as well % rails r "p Rails::Engine.ancestors" [ Rails::Engine, Rails::Railtie, Rails::Initializable, Object, ActiveSupport::Dependencies::Loadable, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject ]
  36. 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 ...
  37. Doesn't have to be a named class # hello/lib/hello.rb module Hello Class.new ::Rails::Engine end
  38. Doesn't have to be wrapped inside the Hello module # hello/lib/hello.rb Class.new ::Rails::Engine
  39. Engine can contain models # hello/app/models/foo.rb class Foo def foo p 'foooo'f end end
  40. How could Rails find Foo?
  41. $LOAD_PATH
  42. eager_load_paths? # hello/lib/hello.rb p Class.new(::Rails::Engine).config.eager_load_paths
  43. Make it a webapp # hello/config/routes.rb Rails.application.routes.draw do resources :foos end # hello/app/controllers/foos_controller.rb class FoosController < ApplicationController def index render text: Foo.new.foo end end
  44. Rails Engine
  45. What can Engines do?
  46. My First Rails Engine
  47. amatsuda/kaminari
  48. Why Engine?
  49. Anatomy of Kaminari kaminari !"" app # !"" helpers # $"" views # $"" kaminari !"" config # $"" locales !"" lib # !"" generators # # $"" kaminari # # $"" templates # $"" kaminari # !"" helpers # $"" models $"" spec
  50. Initial version
  51. v0.1.0
  52. 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
  53. How this code works?
  54. "Kaminari"
  55. "Kaminari"
  56. New version?
  57. Mountable Engines
  58. The Problem
  59. The conflict main_app !"" app # $"" controllers # $"" users_controller.rb $"" my_engine $"" app $"" controllers $"" users_controller.rb
  60. The Solution
  61. Mountable Engine
  62. Anatomy of a Moutable EngineMyEngine !"" app # !"" assets # # !"" images # # # $"" my_engine # # !"" javascripts # # # $"" my_engine # # # $"" application.js # # $"" stylesheets # # $"" my_engine # # $"" application.css # !"" controllers # # $"" my_engine # # $"" application_controller.rb # !"" helpers # # $"" my_engine # # $"" application_helper.rb # !"" mailers # # $"" my_engine # !"" models # # $"" my_engine # $"" views # $"" layouts # $"" my_engine # $"" application.html.erb !"" config # $"" routes.rb !"" lib # !"" my_engine # # !"" engine.rb # # $"" version.rb # $"" my_engine.rb !"" my_engine.gemspec $"" test
  63. Mountable Engine's Engine # my_engine/lib/my_engine/engine.rb module MyEngine class Engine < ::Rails::Engine isolate_namespace MyEngine end end
  64. What can a Mountable Engine do?
  65. Real world examples
  66. ER Diagram
  67. amatsuda/erd
  68. ERD
  69. (DEMO)
  70. A tiny tip
  71. 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
  72. amatsuda/roundabout
  73. I'll be the Roundabout
  74. (DEMO)
  75. What roundabout does
  76. amatsuda/hocus_pocus
  77. hocus_pocus
  78. HocusPocus
  79. (DEMO)
  80. HocusPocus (reprise)
  81. (DEMO)
  82. 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
  83. Engines in an Engine!
  84. Gem::Specification #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&amp;#39; ... s.require_paths = ['lib', 'engines/generator/lib', 'engines/ editor/lib', 'engines/recorder/lib', 'engines/command_line/lib'] ... end
  85. amatsuda/ljax_rails
  86. ljax_rails
  87. Anatomy of ljax_rails ljax_rails !"" app # $"" assets # $"" javascripts # $"" ljax_rails.js.coffee !"" lib # !"" ljax_rails # # !"" action_controller_monkey.rb # # !"" action_dispatch_monkey.rb # # $"" action_view_monkey.rb # $"" ljax_rails.rb !"" ljax_rails.gemspec $"" spec
  88. render :partial <%# app/views/users/index.html.erb %> <%= render 'users'f %>
  89. remote: true <%# app/views/users/index.html.erb %> <%= render 'users'f, remote: true %>
  90. What if the query is very very slow? class UsersController < ApplicationController ... def index if request.ljax? @users = User.all sleep 3 end ennd
  91. Have fun with Rails Engines, and be a great Rails Engineer!

このスライドを共有する

  • このエントリーをはてなブックマークに追加

関連スライド

おすすめスライド

↑