Your SlideShare is downloading. ×
Extracting Plugins And Gems From Rails Apps
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Extracting Plugins And Gems From Rails Apps

3,245
views

Published 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.

Published in: Technology

0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,245
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
42
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

×