40
Josh Nichols technicalpickles.com Extracting Plugins and Gems from Rails application

Extracting Plugins And Gems From Rails Apps

Embed Size (px)

DESCRIPTION

Rails Plugins and Ruby Gems are the basic mechanism of sharing functionality between multiple projects. This talk will go over extracting functionality into a plugin, testing it, sharing it, and converting it to a gem.

Citation preview

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