Extracting Plugins And Gems From Rails Apps

  • 3,183 views
Uploaded on

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 …

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.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
3,183
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
41
Comments
0
Likes
5

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Extracting Plugins and Gems from Rails application Josh Nichols technicalpickles.com
  • 2. Overview • The good of plugins • Reasons for creating your own • Overview of how to extract • Tools for extracting • Case studies • Distributing Josh Nichols technicalpickles.com
  • 3. Ruby and Rails notoriety... • Productivity! • Convention over configuration • Usually sane defaults • 80/20 rule • But, can it get any better? Josh Nichols technicalpickles.com
  • 4. Oh yeah it can. Josh Nichols technicalpickles.com
  • 5. Enter the third party • 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 Josh Nichols technicalpickles.com
  • 6. But guess what? Josh Nichols technicalpickles.com
  • 7. You can be clever too! Josh Nichols technicalpickles.com
  • 8. Why make your own plugin? • 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 Josh Nichols technicalpickles.com
  • 9. What you’d probably want to extract • Model stuff • View helper stuff • Controller stuff Josh Nichols technicalpickles.com
  • 10. Overview of extraction • 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 Josh Nichols technicalpickles.com
  • 11. Overview of extraction • Documentation • RDoc, README • Clean up plugin layout • Test your plugin outside your application • Pull out of your app • Gem Josh Nichols technicalpickles.com
  • 12. Your toolbox: • 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 Josh Nichols technicalpickles.com
  • 13. module ClassMethods def count() 3 end end module InstanceMethods def yell(message) puts quot;#{message.upcase}!!!!quot; end end class Person include InstanceMethods extend ClassMethods end Person.count @person = Person.new() @person.yell quot;I don't know what we're yelling aboutquot; Josh Nichols technicalpickles.com
  • 14. Your toolbox: More modules • 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 Josh Nichols technicalpickles.com
  • 15. 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 end end Josh Nichols technicalpickles.com
  • 16. Toolbox: init.rb • Rails will automatically load this • Add your special sauce here Josh Nichols technicalpickles.com
  • 17. Thinking about how the plugin would be used • Make it always available • Include some modules in init.rb • Include a module • include MyAwesomePlugin • Macro method • acts_as_awesome Josh Nichols technicalpickles.com
  • 18. Toolbox: Always include • 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 } Josh Nichols technicalpickles.com
  • 19. Toolbox: Include a module • Tell users to include in their classes class User < ActiveRecord::Base include Clearance::Models::User end class UsersController < ApplicationController include Clearance::Controllers::UsersController end Josh Nichols technicalpickles.com
  • 20. Toolbox: Macro method • Just a class method • Include InstanceMethods • Extend ClassMethods • Whatever other class stuff you need to do Josh Nichols technicalpickles.com
  • 21. module AwesomePlugin def self.included(base) base.class_eval do extend MacroMethods end end module MacroMethods def acts_as_awesome() include InstanceMethods extend ClassMethods validates_presence_of :awesome end end module InstanceMethods end module ClassMethods end end ActiveRecord::Base.class_eval { include AwesomePlugin } Josh Nichols technicalpickles.com
  • 22. Toolbox: Testing view stuff • Use ActionView::TestCase • Include the module • Just call your methods, test the output • For HTML stuff, assert_dom_equals Josh Nichols technicalpickles.com
  • 23. Tools: Testing model stuff • Fake enough of the environment to get by • Create ActiveRecord::Base migration to sqlite in memory db • Migrate a schema Josh Nichols technicalpickles.com
  • 24. In test/test_helper.rb require 'rubygems' require 'active_record' RAILS_ROOT = File.dirname(__FILE__) require 'logger' RAILS_DEFAULT_LOGGER = Logger.new(quot;#{RAILS_ROOT}/test.logquot;) require File.dirname(__FILE__) + '/../init' # Load the plugin require 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__) + quot;/debug.logquot;) ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'plugin_test']) load(File.dirname(__FILE__) + quot;/schema.rbquot;) if File.exist?(File.dirname(__FILE__) + quot;/ schema.rbquot;) Josh Nichols technicalpickles.com
  • 25. Toolbox: Other testing • Create a rails app within your plugin test layout • test/rails_root • Update Rakefile to run tests from within the test/ rails_root Josh Nichols technicalpickles.com
  • 26. Rakefile 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 = false end Josh Nichols technicalpickles.com
  • 27. Case Study: content_given View helpers, always included http://github.com/technicalpickles/content_given Josh Nichols technicalpickles.com
  • 28. Case study: safety_valve Controller stuff, opt in by including module http://github.com/technicalpickles/safety_valve Josh Nichols technicalpickles.com
  • 29. Case study: has_markup Model stuff, macro method http://github.com/technicalpickles/has_markup Josh Nichols technicalpickles.com
  • 30. Distributing • GitHub • Free • Easy to collaborate with others • script/plugin install git://github.com/technicalpickles/ ambitious-sphinx.git • Also supports generating RubyGems Josh Nichols technicalpickles.com
  • 31. Distributing: Gems • Create a gemspec for your project • Enable RubyGems for your repository • http://hasmygembuiltyet.org/ Josh Nichols technicalpickles.com
  • 32. Gem::Specification.new do |s| s.name = %q{jeweler} s.version = quot;0.1.1quot; s.required_rubygems_version = Gem::Requirement.new(quot;>= 0quot;) if s.respond_to? :required_rubygems_version= s.authors = [quot;Josh Nicholsquot;, quot;Dan Croakquot;] s.date = %q{2008-10-14} s.description = %q{Simple and opinionated helper for creating Rubygem projects on GitHub} s.email = %q{josh@technicalpickles.com} s.files = [quot;Rakefilequot;, quot;README.markdownquot;, quot;TODOquot;, quot;VERSION.ymlquot;, quot;lib/jewelerquot;, quot;lib/ jeweler/active_support.rbquot;, quot;lib/jeweler/bumping.rbquot;, quot;lib/jeweler/errors.rbquot;, quot;lib/ jeweler/gemspec.rbquot;, quot;lib/jeweler/singleton.rbquot;, quot;lib/jeweler/tasks.rbquot;, quot;lib/jeweler/ versioning.rbquot;, quot;lib/jeweler.rbquot;, quot;test/jeweler_test.rbquot;, quot;test/test_helper.rbquot;] s.homepage = %q{http://github.com/technicalpickles/jeweler} s.require_paths = [quot;libquot;] s.rubygems_version = %q{1.2.0} s.summary = %q{Simple and opinionated helper for creating Rubygem projects on GitHub} end Josh Nichols technicalpickles.com
  • 33. $ sudo gem install technicalpickles-jeweler Josh Nichols technicalpickles.com
  • 34. Distributing: Versioning • 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 Josh Nichols technicalpickles.com
  • 35. spec = Gem::Specification.new do |s| s.name = quot;shouldaquot; s.version = Thoughtbot::Shoulda::VERSION s.summary = quot;Making tests easy on the fingers and eyesquot; s.homepage = quot;http://thoughtbot.com/projects/shouldaquot; s.rubyforge_project = quot;shouldaquot; s.files = FileList[quot;[A-Z]*quot;, quot;{bin,lib,rails,test}/**/*quot;] s.executables = s.files.grep(/^bin/) { |f| File.basename(f) } s.has_rdoc = true s.extra_rdoc_files = [quot;README.rdocquot;, quot;CONTRIBUTION_GUIDELINES.rdocquot;] s.rdoc_options = [quot;--line-numbersquot;, quot;--inline-sourcequot;, quot;--mainquot;, quot;README.rdocquot;] s.authors = [quot;Tammer Salehquot;] s.email = quot;tsaleh@thoughtbot.comquot; s.add_dependency quot;activesupportquot;, quot;>= 2.0.0quot; end desc quot;Generate a gemspec file for GitHubquot; task :gemspec do File.open(quot;#{spec.name}.gemspecquot;, 'w') do |f| f.write spec.to_ruby end end Josh Nichols technicalpickles.com
  • 36. Distributing: Versioning • 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? Josh Nichols technicalpickles.com
  • 37. Jeweler Craft the perfect gem http://github.com/technicalpickles/jeweler Josh Nichols technicalpickles.com
  • 38. Jeweler • Rake tasks for creating and validating gemspec • Rake tasks for bumping the version • Will automatically write out updated gemspec Josh Nichols technicalpickles.com
  • 39. $ rake version (in /Users/nichoj/Projects/jeweler) Current version: 0.1.1 $ rake gemspec (in /Users/nichoj/Projects/jeweler) Generated: jeweler.gemspec jeweler.gemspec is valid. $ rake version:bump:minor (in /Users/nichoj/Projects/jeweler) Current version: 0.1.1 Wrote to VERSION.yml: 0.2.0 Generated: jeweler.gemspec $ rake version:bump:patch (in /Users/nichoj/Projects/jeweler) Current version: 0.2.0 Wrote to VERSION.yml: 0.2.1 Generated: jeweler.gemspec $ rake version:bump:major (in /Users/nichoj/Projects/jeweler) Current version: 0.2.1 Wrote to VERSION.yml: 1.0.0 Generated: jeweler.gemspec Josh Nichols technicalpickles.com
  • 40. Rakefile begin require 'rubygems' require 'jeweler' gemspec = Gem::Specification.new do |s| s.name = quot;has_markupquot; s.summary = quot;Manage markup close to home... right in the model! Caching, validation, etcquot; s.email = quot;josh@technicalpickles.comquot; s.homepage = quot;http://github.com/technicalpickles/has_markupquot; s.description = quot;Manage markup close to home... right in the model! Caching, validation, etcquot; s.authors = [quot;Josh Nicholsquot;] s.files = FileList[quot;[A-Z]*.*quot;, quot;{generators,lib,test,spec}/**/*quot;] end Jeweler.craft(gemspec) rescue LoadError puts quot;Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.comquot; end Josh Nichols technicalpickles.com