Slide 1

Slide 1 text

Rails Plugins Authors' Guide Akira Matsuda

Slide 2

Slide 2 text

self name: Akira from: Tokyo, Japan GitHub: amatsuda Twitter: @a_matsuda

Slide 3

Slide 3 text

I Work on Some OSS Projects

Slide 4

Slide 4 text

I Work on Some OSS Projects Ruby

Slide 5

Slide 5 text

I Work on Some OSS Projects Ruby Rails

Slide 6

Slide 6 text

Gems kaminari active_decorator motorhead stateful_enum action_args (asakusarb) Find more on http:/ /trending.rubyasia.com/

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Asakusa.rb Meetup https:/ /twitter.com/lindaliukas/status/717379600175603712

Slide 9

Slide 9 text

RubyKaigi = ͟ ͟͞͞

Slide 10

Slide 10 text

RubyKaigi 2015, 2016 Chief Organizer

Slide 11

Slide 11 text

RubyKaigi 2016 September 8..10 In Kyoto

Slide 12

Slide 12 text

Coming Soon! CFP Sponsorships Tickets

Slide 13

Slide 13 text

begin

Slide 14

Slide 14 text

Rails Plugins

Slide 15

Slide 15 text

Who Here Have Never Created and Published a Rails Plugin?

Slide 16

Slide 16 text

What Is a Rails Plugin? A RubyGem Adds some features to your Rails apps Written in Ruby (or C)

Slide 17

Slide 17 text

History of Rails Plugins

Slide 18

Slide 18 text

Rails 1, Rails 2 Placed in vendor/plugins directory No package management Usually served via SVN server on the Internet % ./script/plugin install svn:/ /...

Slide 19

Slide 19 text

Rails 2.1 Ruby Gem as a Rails plugin config.gem in config/ environment.rb "Dependency Hell" problem

Slide 20

Slide 20 text

Rails 2 Era GitHub hosted gems http:/ /gems.github.com as a gem source username-gemname as a Gem config.gem 'mislav-will_paginate', :lib => 'will_paginate'

Slide 21

Slide 21 text

Rails 3.0 The Rails 3 team (@carlhuda) created Bundler No more dependency hell!

Slide 22

Slide 22 text

Rails 4.0 vendor/plugins' Death

Slide 23

Slide 23 text

Dealing with Plugins Is So Easy Today Your Gemfile.lock knows everything bundle show will take you to any bundled gem An IDE like RubyMine would also help exploring the gems

Slide 24

Slide 24 text

Plugins Are Your App Every Rails plugin that you bundle to your app is a part of your application code You need to read them, understand them, patch them, and sometimes maintain them

Slide 25

Slide 25 text

Learn How It Works So you can read the code, and understand the code

Slide 26

Slide 26 text

A Typical Rails Plugin's Structure .gemspec lib exe (bin) test, spec ext (C extension source code)

Slide 27

Slide 27 text

How Do Rails Plugins Work? required from the Rails app

Slide 28

Slide 28 text

require

Slide 29

Slide 29 text

Ruby's require require 'foo'

Slide 30

Slide 30 text

require and $LOAD_PATH require 'foo' scans through each $LOAD_PATH directory until the first 'foo.rb' (or .so, .o, .dll) were found

Slide 31

Slide 31 text

RubyGems' require If it fails to require 'foo' And there's a Gem named foo installed Adds foo Gem's lib directory to $LOAD_PATH, and then requires it

Slide 32

Slide 32 text

RubyGems Redefines require Behaviour This is made possible because require is nothing but just a Ruby method (Kernel#require). So you can monkey-patch and add a such feature

Slide 33

Slide 33 text

Bundler's setup & require bundler also extends Kernel#require

Slide 34

Slide 34 text

Bundler's setup & require Adds the bundled Gems' lib directories to $LOAD_PATH requires only from $LOAD_PATH. Not from all the installed gems

Slide 35

Slide 35 text

When Requiring 'foo' Each bundled Gem's lib directory is in $LOAD_PATH And require tries to find 'foo.rb' in each $LOAD_PATH

Slide 36

Slide 36 text

autoload autoload :Foo, './foo.rb'

Slide 37

Slide 37 text

Ruby's autoload A technique to save memory usage, shorten the app's startup time

Slide 38

Slide 38 text

Kernel#autoload Takes a Symbol and a filename "Registers filename to be loaded (using Kernel::require) the first time that module (which may be a String or a symbol) is accessed in the namespace of mod." https:/ /github.com/ruby/ruby/blob/trunk/ load.c

Slide 39

Slide 39 text

Rails's autoload Convention When a constant is missing, Rails automatically looks up the autoload paths for the constant_name.underscore e.g. User.find(id) but User was still not loaded, Rails automatically finds app/models/ user.rb and loads it So we don't have to manually write require everywhere

Slide 40

Slide 40 text

The Entry Point of a Gem gemdir/lib/gemname.rb This file will be loaded when require 'foo' was executed (usually performed by Bundler) When you read a plugin code, always start reading from this file You can put lib/bar.rb, lib/baz.rb or anything, but that would break other Gems. Do NEVER do that.

Slide 41

Slide 41 text

The Gem Namespace For the Foo gem, Foo module becomes the gem's namespace gemdir/lib/foo/ directory is where you can physically put the code You can create any directories under your gemdir/lib/ directory, but don't create anything else Do never invade other plugins' namespace

Slide 42

Slide 42 text

_ and - _ just connects multiple words action_args => 'action_args' - is for namespacing. Usually when extending another existing gem. kaminari-sinatra => 'kaminari/ sinatra'

Slide 43

Slide 43 text

Now You Know How to Read You learned basic rules of RubyGems Then the next step is to know how to write

Slide 44

Slide 44 text

Examples

Slide 45

Slide 45 text

Extending Rails’ Components

Slide 46

Slide 46 text

Railties, Initializers, and AS.on_load hooks ActiveSupport.on_load :action_controller do (do something) end

Slide 47

Slide 47 text

AS.on_load hooks The block inside AS.on_load will be executed when AC::Base was loaded

Slide 48

Slide 48 text

Extending ActionPack action_args

Slide 49

Slide 49 text

action_args https:/ /github.com/asakusarb/ action_args Rails version of merb-action-args def show(id)
 User.find id
 end

Slide 50

Slide 50 text

Adding Parameters to Controller Methods module ActionArgs module AbstractControllerMethods def send_action(method_name, *args) return super unless args.empty? return super unless defined?(params) strengthen_params! method_name values = extract_method_arguments_from_params method_name super method_name, *values ennnd

Slide 51

Slide 51 text

Extending ActionView html5_validators

Slide 52

Slide 52 text

html5_validators https:/ /github.com/ amatsuda/html5_validators Model validation => HTML5 Form Validation

Slide 53

Slide 53 text

Adding an Attribute to the text_field Helper module Html5Validators::ActionViewExtension::PresenceValidator def render if object.class.ancestors.include?(ActiveModel::Validations && ... @options["required"] ||= @options[:required] || object.class.attribute_required?(@method_name) end super ennd ActionView::Helpers::Tags::TextField.prepend Html5Validators::ActionViewExtension::PresenceValidator

Slide 54

Slide 54 text

Extending ActiveRecord stateful_enum

Slide 55

Slide 55 text

stateful_enum https:/ /github.com/amatsuda/ stateful_enum A state machine plugin on top of AR::Enum Extends enum method to take a block

Slide 56

Slide 56 text

Extending AR::Base module StatefulEnum class Railtie < ::Rails::Railtie ActiveSupport.on_load :active_record do ::ActiveRecord::Base.extend StatefulEnum::ActiveRecordEnumExtension end end end

Slide 57

Slide 57 text

Adding a Feature to enum module StatefulEnum module ActiveRecordEnumExtension def enum(definitions, &block) enum = super definitions if block definitions.each_key do |column| states = enum[column] StatefulEnum::Machine.new self, column, (states. (Hash) ? states.keys : states), prefix, suffix, &block ennnnnd

Slide 58

Slide 58 text

Creating a New Layer active_decorator

Slide 59

Slide 59 text

active_decorator https:/ /github.com/ amatsuda/active_decorator RAILS_ROOT/app/ decorators/ as better view helpers

Slide 60

Slide 60 text

app/* for a New Layer Nothing special It should just work Rails would automatically load the files there

Slide 61

Slide 61 text

Generator i18n_generators

Slide 62

Slide 62 text

i18n_generators https:/ /github.com/ amatsuda/i18n_generators Internationalizes your Rails app

Slide 63

Slide 63 text

i18n_generators % rails g i18n tl #=> generates Tagalog translated I18n resource

Slide 64

Slide 64 text

Creating a Generator Place a .rb file with a Rails::Generator::Base class inside lib/generators/ directory.

Slide 65

Slide 65 text

Creating a Generator lib !"" generators # !"" i18n # # %"" i18n_generator.rb

Slide 66

Slide 66 text

Rake Tasks traceroute

Slide 67

Slide 67 text

traceroute https:/ /github.com/ amatsuda/traceroute A simple Rake task that reports unused routes and controller actions in the app

Slide 68

Slide 68 text

Creating a Rake Task Place a .rake file inside lib/ tasks directory.

Slide 69

Slide 69 text

Creating a Rake Task lib !"" tasks # %"" traceroute.rake %"" traceroute.rb

Slide 70

Slide 70 text

Rails Engine erd

Slide 71

Slide 71 text

Erd https:/ /github.com/amatsuda/erd Database ER diagram viewer / drawer at http:/ /localhost:3000/erd A Rails Engine just looks like a Rails app lib/erd/engine.rb

Slide 72

Slide 72 text

Erd erd !"" Gemfile !"" app # !"" assets # !"" controllers # # %"" erd # # %"" erd_controller.rb # %"" views # %"" erd # %"" erd # %"" index.html.erb !"" config # %"" routes.rb !"" erd.gemspec !"" lib # !"" erd # # !"" engine.rb # # %"" railtie.rb # %"" erd.rb %"" vendor %"" assets

Slide 73

Slide 73 text

Erd module Erd class Engine < ::Rails::Engine isolate_namespace Erd end end

Slide 74

Slide 74 text

More on Rails Engines Motorhead

Slide 75

Slide 75 text

Motorhead https:/ /github.com/ amatsuda/motorhead A feature prototyping framework

Slide 76

Slide 76 text

Prototyping a New Feature Extending an existing controller using Rails Engines You can add a new feature prototype without touching the main app.

Slide 77

Slide 77 text

Prototyping a New Feature class NewFeature::PostsController < ::PostsController def index super @something_new = ... ennd

Slide 78

Slide 78 text

How Motorhead Lets an Engine Intercept the Request module Motorhead module Engine extend ActiveSupport::Concern included do isolate_namespace self.parent ActiveSupport.on_load :after_initialize do Rails.application.routes.prepend do mount engine_kls, at: '/' ennnnnd

Slide 79

Slide 79 text

Some More Rails Plugin Implementation Techniques

Slide 80

Slide 80 text

AMC vs prepend (Rails 5) alias_method_chain was introduced for Rails 1 Ruby has Module#prepend as a core feature since Ruby 2.0 alias_method_chain has been deprecated in Rails 5.0 Rails 5 plugins should use Module#prepend instead Rails 5 plugins should drop Ruby 1 support

Slide 81

Slide 81 text

Designing and Implementing DSL stateful_enum

Slide 82

Slide 82 text

instance_eval, instance_exec, and block.call Take a look at the code!

Slide 83

Slide 83 text

Do Never Monkey-patch Core Classes! SimpleCov https:/ /github.com/ colszowka/simplecov/pull/ 449

Slide 84

Slide 84 text

Do Never Monkey-patch Core Classes Array.send :include, SimpleCov::ArrayMergeHelper Hash.send :include, SimpleCov::HashMergeHelper

Slide 85

Slide 85 text

Instead,

Slide 86

Slide 86 text

Instead, Use Refinements!

Slide 87

Slide 87 text

Refinements as a Better monkey-patching Strategy # lib/action_args/params_handler.rb module ActionArgs module ParamsHandler refine AbstractController::Base do def extract_method_arguments_from_params(method_ name) ... ennnnd

Slide 88

Slide 88 text

Refinements as a Better monkey-patching Strategy using ActionArgs::ParamsHandler module ActionArgs module AbstractControllerMethods def send_action(method_name, *args) ... values = extract_method_arguments_from_params method_name super method_name, *values ennnd AbstractController::Base.send :prepend, ActionArgs::AbstractControllerMethods

Slide 89

Slide 89 text

rbenv Plugin && Gem in One Codebase gem-src

Slide 90

Slide 90 text

gem-src https:/ /github.com/amatsuda/gem-src A RubyGems hook that git clones every repo that you gem install or bundle install Makes you ready to send PRs anytime

Slide 91

Slide 91 text

gem-src # etc/rbenv.d/exec/~gem-src.bash cwd="$PWD" cd "${BASH_SOURCE%/*}/../../../lib" # Make sure `rubygems_plugin.rb` is discovered by RubyGems by adding its directory to Ruby's load path. export RUBYLIB="$PWD:$RUBYLIB" cd "$cwd"

Slide 92

Slide 92 text

Naming Gems is Hard There's a certain reason that people tend to choose weird Gem names Because a Gem name becomes a top-level Module We need to choose a name that would not overlap with model names in the apps Learn from Journey's case

Slide 93

Slide 93 text

"Namespace Conflict: Journey is a very generic name" https:/ /github.com/rails/ journey/issues/49 ::Journey => ActionDispatch::Journey

Slide 94

Slide 94 text

Merging Your Plugin into Rails Core everywhere

Slide 95

Slide 95 text

everywhere https:/ /github.com/ amatsuda/everywhere where.not syntax for ActiveRecord 3

Slide 96

Slide 96 text

I Created the Gem First, Proved that It Works, Then Sent a PR https:/ /github.com/rails/ rails/pull/8332

Slide 97

Slide 97 text

Have Fun with
 Rails Plugins! Read the plugins that you use Hack on your ideas Solve your problems Share your solutions!

Slide 98

Slide 98 text

end