Extracting Plugins And Gems From Rails Apps

  • Published on

  • View

  • Download


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.


<ul><li>1.Extracting Plugins and Gems from RailsapplicationJosh Nichols technicalpickles.com </li></ul> <p>2. Overview The good of plugins Reasons for creating your own Overview of how to extract Tools for extracting Case studies DistributingJosh Nicholstechnicalpickles.com 3. Ruby and Rails notoriety... Productivity! Convention over conguration 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 its goodness, Rails lacks a lot of functionalityyoud nd in an webapp Pagination, Authentication, Etc Do you really want to have to implement this for everyapp? The Internet is full of clever bastards that have guredout good practices for these, and released them asplugins and gemsJosh Nichols technicalpickles.com 6. But guess what? Josh Nicholstechnicalpickles.com 7. You can be clevertoo! Josh Nicholstechnicalpickles.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 nextJosh Nichols technicalpickles.com 9. What youd probably want toextract 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 hasgood 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 GemJosh Nicholstechnicalpickles.com 12. Your toolbox: Modules Group related things Cant 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 ClassMethodsdef count()3endend module InstanceMethodsdef yell(message)puts quot;#{message.upcase}!!!!quot;endend class Personinclude InstanceMethodsextend ClassMethodsend Person.count@person = Person.new()@person.yell quot;I don't know what we're yelling aboutquot; Josh Nicholstechnicalpickles.com 14. Your toolbox: More modules In your modules methods, you have access to everythingit was mixed into Theres 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 methodsJosh Nichols technicalpickles.com 15. module MyPlugindef self.included(base)base.class_eval doinclude InstanceMethodsextend ClassMethodsvalidates_presence_of :awesomeendend module InstanceMethodsend module ClassMethodsendend Josh Nicholstechnicalpickles.com 16. Toolbox: init.rb Rails will automatically load this Add your special sauce here Josh Nichols technicalpickles.com 17. Thinking about how the pluginwould be used Make it always available Include some modules in init.rb Include a module include MyAwesomePlugin Macro method acts_as_awesomeJosh 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 &lt; ActiveRecord::Baseinclude Clearance::Models::Userend class UsersController &lt; ApplicationControllerinclude Clearance::Controllers::UsersControllerend 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 Nicholstechnicalpickles.com 21. module AwesomePlugin def self.included(base) base.class_eval do extend MacroMethods end endmodule MacroMethods def acts_as_awesome() include InstanceMethods extend ClassMethods validates_presence_of :awesome end end module InstanceMethodsendmodule ClassMethodsendendActiveRecord::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 Nicholstechnicalpickles.com 23. Tools: Testing model stuff Fake enough of the environment to get by Create ActiveRecord::Base migration to sqlite inmemory db Migrate a schema Josh Nichols technicalpickles.com 24. In test/test_helper.rbrequire '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 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__) + 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 Nicholstechnicalpickles.com 25. Toolbox: Other testing Create a rails app within your plugin test layout test/rails_root Update Rakele to run tests from within the test/rails_root Josh Nichols technicalpickles.com 26. Rakele test_files_pattern = 'test/rails_root/test/{unit,functional,other}/**/*_test.rb'Rake::TestTask.new do |t|t.libs = 0quot;) ifs.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 onGitHub}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 Nicholstechnicalpickles.com 33. $ sudo gem install technicalpickles-jeweler Josh Nicholstechnicalpickles.com 34. Distributing: Versioning Update gemspec Update les Push to github Kinda annoying to maintain les Can maintain it with Rake Give Gem::Spec Rakes FileList to generate list of le Write the spec outJosh Nicholstechnicalpickles.com 35. spec = Gem::Specification.new do |s|s.name= quot;shouldaquot;s.version = Thoughtbot::Shoulda::VERSIONs.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;&gt;= 2.0.0quot;end desc quot;Generate a gemspec file for GitHubquot;task :gemspec doFile.open(quot;#{spec.name}.gemspecquot;, 'w') do |f|f.write spec.to_rubyendend Josh Nicholstechnicalpickles.com 36. Distributing: Versioning Update Rakeles Gem::Specications version Run rake gemspec Commit and push Easy to forget to keep Rakele and gemspec in sync Can it get easier? Josh Nicholstechnicalpickles.com 37. JewelerCraft the perfect gem http://github.com/technicalpickles/jeweler Josh Nicholstechnicalpickles.com 38. Jeweler Rake tasks for creating and validating gemspec Rake tasks for bumping the version Will automatically write out updated gemspec Josh Nicholstechnicalpickles.com 39. $ 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 Josh Nichols technicalpickles.com 40. Rakele 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 </p>


View more >