Transcript
Page 1: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Extracting Plugins and Gems from Rails

application

Page 2: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• The good of plugins

• Reasons for creating your own

• Overview of how to extract

• Tools for extracting

• Case studies

• Distributing

Overview

Page 3: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Productivity!

• Convention over configuration

• Usually sane defaults

• 80/20 rule

• But, can it get any better?

Ruby and Rails notoriety...

Page 4: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Oh yeah it can.

Page 5: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Despite it’s goodness, Rails lacks a lot of functionality you’d find in an webapp

• Pagination, Authentication, Etc

• Do you really want to have to implement this for every app?

• The Internet is full of clever bastards that have figured out good practices for these, and released them as plugins and gems

Enter the third party

Page 6: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

But guess what?

Page 7: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

You can be clever too!

Page 8: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Lets you clean up your code

• Focus on core business concerns, not the other stuff

• Re-use within your application

• DRY

• Re-use between applications

• Extract something useful out of one app...

• ... and have a headstart on the next

Why make your own plugin?

Page 9: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Model stuff

• View helper stuff

• Controller stuff

What you’d probably want to extract

Page 10: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Recognize need/want for extracting

• Make sure the functionality you want to extract has good coverage

• script/generate plugin to start a plugin

• Move code into the plugin

• Make your code use the plugin

• Make sure tests still pass

Overview of extraction

Page 11: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Documentation

• RDoc, README

• Clean up plugin layout

• Test your plugin outside your application

• Pull out of your app

• Gem

Overview of extraction

Page 12: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Modules

• Group related things

• Can’t create instances of them

• Can ‘mixin’ to classes

• ‘include’ a module into a class to add instance methods

• ‘extend’ a module into a class to add class methods

Your toolbox:

Page 13: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

module ClassMethods def count() 3 endend

module InstanceMethods def yell(message) puts "#{message.upcase}!!!!" endend

class Person include InstanceMethods extend ClassMethodsend

Person.count@person = Person.new()@person.yell "I don't know what we're yelling about"

Page 14: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• In your module’s methods, you have access to everything it was mixed into

• There’s a callback for when a module is included

• Gives you access to the class that included the module

• Use this to include/extend other modules

• ... or call class methods

Your toolbox: More modules

Page 15: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

module MyPlugin def self.included(base) base.class_eval do include InstanceMethods extend ClassMethods validates_presence_of :awesome end end

module InstanceMethods end

module ClassMethods endend

Page 16: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Rails will automatically load this

• Add your special sauce here

Toolbox: init.rb

Page 17: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Make it always available

• Include some modules in init.rb

• Include a module

• include MyAwesomePlugin

• Macro method

• acts_as_awesome

Thinking about how the plugin would be used

Page 18: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Usually do this in init.rb

• For Model:

• ActiveRecord::Base.class_eval { include MyPlugin }

• For Controller:

• ActionController::Base.class_eval { include MyPlugin }

• For View:

• ActionView::Base.class_eval { include MyPlugin }

Toolbox: Always include

Page 19: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Tell users to include in their classes

Toolbox: Include a module

class User < ActiveRecord::Base include Clearance::Models::Userend

class UsersController < ApplicationController include Clearance::Controllers::UsersControllerend

Page 20: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Just a class method

• Include InstanceMethods

• Extend ClassMethods

• Whatever other class stuff you need to do

Toolbox: Macro method

Page 21: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

module AwesomePlugindef self.included(base)

base.class_eval doextend MacroMethods

endend

module MacroMethodsdef acts_as_awesome()

include InstanceMethodsextend ClassMethodsvalidates_presence_of :awesome

endend

module InstanceMethodsendmodule ClassMethodsend

endActiveRecord::Base.class_eval { include AwesomePlugin }

Page 22: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Use ActionView::TestCase

• Include the module

• Just call your methods, test the output

• For HTML stuff, assert_dom_equals

Toolbox: Testing view stuff

Page 23: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Fake enough of the environment to get by

• Create ActiveRecord::Base migration to sqlite in memory db

• Migrate a schema

Tools: Testing model stuff

Page 24: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

require 'rubygems'require 'active_record'

RAILS_ROOT = File.dirname(__FILE__)require 'logger'RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/test.log")

require File.dirname(__FILE__) + '/../init' # Load the pluginrequire File.dirname(__FILE__) + '/post.rb' # Test model

config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'plugin_test'])

load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")

In test/test_helper.rb

Page 25: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Create a rails app within your plugin test layout

• test/rails_root

• Update Rakefile to run tests from within the test/rails_root

Toolbox: Other testing

Page 26: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

test_files_pattern = 'test/rails_root/test/{unit,functional,other}/**/*_test.rb'Rake::TestTask.new do |t| t.libs << 'lib' t.pattern = test_files_pattern t.verbose = falseend

Rakefile

Page 27: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

View helpers, always includedhttp://github.com/technicalpickles/content_given

Case Study: content_given

Page 28: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Controller stuff, opt in by including modulehttp://github.com/technicalpickles/safety_valve

Case study: safety_valve

Page 29: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Model stuff, macro methodhttp://github.com/technicalpickles/has_markup

Case study: has_markup

Page 30: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• GitHub

• Free

• Easy to collaborate with others

• script/plugin install git://github.com/technicalpickles/ambitious-sphinx.git

• Also supports generating RubyGems

Distributing

Page 31: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Create a gemspec for your project

• Enable RubyGems for your repository

• http://hasmygembuiltyet.org/

Distributing: Gems

Page 32: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Gem::Specification.new do |s| s.name = %q{jeweler} s.version = "0.1.1"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Josh Nichols", "Dan Croak"] s.date = %q{2008-10-14} s.description = %q{Simple and opinionated helper for creating Rubygem projects on GitHub} s.email = %q{[email protected]} s.files = ["Rakefile", "README.markdown", "TODO", "VERSION.yml", "lib/jeweler", "lib/jeweler/active_support.rb", "lib/jeweler/bumping.rb", "lib/jeweler/errors.rb", "lib/jeweler/gemspec.rb", "lib/jeweler/singleton.rb", "lib/jeweler/tasks.rb", "lib/jeweler/versioning.rb", "lib/jeweler.rb", "test/jeweler_test.rb", "test/test_helper.rb"] s.homepage = %q{http://github.com/technicalpickles/jeweler} s.require_paths = ["lib"] s.rubygems_version = %q{1.2.0} s.summary = %q{Simple and opinionated helper for creating Rubygem projects on GitHub}end

Page 33: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

$ sudo gem install technicalpickles-jeweler

Page 34: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Update gemspec

• Update files

• Push to github

• Kinda annoying to maintain files

• Can maintain it with Rake

• Give Gem::Spec Rake’s FileList to generate list of file

• Write the spec out

Distributing: Versioning

Page 35: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

spec = Gem::Specification.new do |s| s.name = "shoulda" s.version = Thoughtbot::Shoulda::VERSION s.summary = "Making tests easy on the fingers and eyes" s.homepage = "http://thoughtbot.com/projects/shoulda" s.rubyforge_project = "shoulda"

s.files = FileList["[A-Z]*", "{bin,lib,rails,test}/**/*"] s.executables = s.files.grep(/^bin/) { |f| File.basename(f) }

s.has_rdoc = true s.extra_rdoc_files = ["README.rdoc", "CONTRIBUTION_GUIDELINES.rdoc"] s.rdoc_options = ["--line-numbers", "--inline-source", "--main", "README.rdoc"]

s.authors = ["Tammer Saleh"] s.email = "[email protected]"

s.add_dependency "activesupport", ">= 2.0.0"end

desc "Generate a gemspec file for GitHub"task :gemspec do File.open("#{spec.name}.gemspec", 'w') do |f| f.write spec.to_ruby endend

Page 36: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Update Rakefile’s Gem::Specification’s version

• Run ‘rake gemspec’

• Commit and push

• Easy to forget to keep Rakefile and gemspec in sync

• Can it get easier?

Distributing: Versioning

Page 37: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

Craft the perfect gemhttp://github.com/technicalpickles/jeweler

Jeweler

Page 38: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

• Rake tasks for creating and validating gemspec

• Rake tasks for bumping the version

• Will automatically write out updated gemspec

Jeweler

Page 39: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

$ rake version(in /Users/nichoj/Projects/jeweler)Current version: 0.1.1

$ rake gemspec(in /Users/nichoj/Projects/jeweler)Generated: jeweler.gemspecjeweler.gemspec is valid.

$ rake version:bump:minor(in /Users/nichoj/Projects/jeweler)Current version: 0.1.1Wrote to VERSION.yml: 0.2.0Generated: jeweler.gemspec

$ rake version:bump:patch(in /Users/nichoj/Projects/jeweler)Current version: 0.2.0Wrote to VERSION.yml: 0.2.1Generated: jeweler.gemspec

$ rake version:bump:major(in /Users/nichoj/Projects/jeweler)Current version: 0.2.1Wrote to VERSION.yml: 1.0.0Generated: jeweler.gemspec

Page 40: Extracting Plugins And Gems From Rails Apps

Josh Nichols technicalpickles.com

begin require 'rubygems' require 'jeweler' gemspec = Gem::Specification.new do |s| s.name = "has_markup" s.summary = "Manage markup close to home... right in the model! Caching, validation, etc" s.email = "[email protected]" s.homepage = "http://github.com/technicalpickles/has_markup" s.description = "Manage markup close to home... right in the model! Caching, validation, etc" s.authors = ["Josh Nichols"] s.files = FileList["[A-Z]*.*", "{generators,lib,test,spec}/**/*"] end Jeweler.craft(gemspec)rescue LoadError puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"end

Rakefile