Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby on Rails: Plugin Development 101 (...and some...)

Ruby on Rails: Plugin Development 101 (...and some...)

Public archival of a presentation I did back in 2009 for a small crowd of university students in Athens, Greece.

Jim Myhrberg

October 17, 2009
Tweet

More Decks by Jim Myhrberg

Other Decks in Programming

Transcript

  1. install.rb is executed once during installation. init.rb is the only

    file included by Rails. The lib folder is your sanctuary. Aside from hello_world.rb, place all Ruby source files in lib/hello_world/ to avoid naming collisions.
  2. install.rb copies collecta.yml to Rails’ config folder during installation. init.rb

    requires lib/collecta.rb, loads settings from installed collecta.yml and applies them to the Collecta class. lib/collecta.rb is the ‘heart’ of the plug-in. Single-file Plug-in
  3. install.rb require "rubygems" require "fileutils" dir = File.dirname(__FILE__) templates =

    File.join(dir, "templates") files = [ File.join("config", "collecta.yml") ] files.each do |file| if !File.exist?(File.join(RAILS_ROOT, file)) FileUtils.cp File.join(templates, file), File.join(RAILS_ROOT, file) end end
  4. init.rb if defined? Rails require "collecta" config_file = File.join(RAILS_ROOT, "config",

    "collecta.yml") if File.exist?(config_file) config = YAML.load_file(config_file) if !config[RAILS_ENV.to_s].nil? && !config[RAILS_ENV.to_s]["api_key"].nil? Collecta.api_key = config[RAILS_ENV.to_s]["api_key"] end end end
  5. collecta.rb require "rubygems" require "net/http" require "uri" require "cgi" require

    "json" require "xml" class Collecta @@api_key = nil @@api_url = "http://api.collecta.com/search" # rest of the class... end
  6. init.rb requires all needed files from lib folder, and calls

    an init method too boot the plugin. Notice how all files are located under lib/facebooker_plus/. This avoids any naming collisions from other plug-ins, gems, or system. Multi-file Plug-in
  7. init.rb if defined? Rails if defined? Facebooker require 'facebooker_plus/facebooker_plus' require

    'facebooker_plus/rails/fb_sig_add' require 'facebooker_plus/rails/controller' require 'facebooker_plus/rails/helper' require 'facebooker_plus/extensions/action_controller' require 'facebooker_plus/extensions/action_view' require 'facebooker_plus/extensions/session' FacebookerPlus::Base.init(defined?(config) ? config : nil) else STDERR.puts "** [FacebookerPlus] ERROR: Please load Facebooker before Facebooker Plus.\n" end end
  8. helper.rb module FacebookerPlus module Rails module Helper def url_for(options =

    {}) options.is_a?(Hash) ? super(options) : fb_sig_add(super(options)) end def form_for(record_or_name_or_array, *args, &proc) args[0][:url] = fb_sig_add(args[0][:url]) if !args[0][:url].nil? super(record_or_name_or_array, *args, &proc) end end end end
  9. Makes it easy to control different aspects of your plug-

    in from within controllers. Easily create global before/after filters which run from your plug-in. Create class methods to enable/disable your plugin on a per-controller basis.
  10. action_controller.rb module ::ActionController class Base def self.inherited_with_facebooker_plus(subclass) inherited_without_facebooker_plus(subclass) if subclass.to_s

    == "ApplicationController" subclass.send(:include, FacebookerPlus::Rails::Controller) end end class << self alias_method_chain :inherited, :facebooker_plus end end end
  11. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def send_p3p_headers if !params[:fb_sig_in_iframe].blank? headers['P3P'] = 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"' end end def url_for(options = {}) fb_sig_add(super(options)) rescue super(options) end module ClassMethods def init_facebooker_plus(options = {}) before_filter :send_p3p_headers end end end end end
  12. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def set_facebooker_plus_options(options = {}) @facebooker_plus_options = options end def apply_facebooker_options(options = {}) if @facebooker_plus_options.has_key?(:app_class) then end end def create_session_cookie_if_needed # magic happens here end module ClassMethods def init_facebooker_plus(options = {}) before_filter { |controller| controller.set_facebooker_plus_options(options) } before_filter :create_session_cookie_if_needed before_filter :apply_facebooker_options end end end end end
  13. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def set_facebooker_plus_options(options = {}) @facebooker_plus_options = options end def apply_facebooker_options(options = {}) if @facebooker_plus_options.has_key?(:app_class) then end end def create_session_cookie_if_needed # magic happens here end module ClassMethods def init_facebooker_plus(options = {}) before_filter { |controller| controller.set_facebooker_plus_options(options) } before_filter :create_session_cookie_if_needed before_filter :apply_facebooker_options end end end end end
  14. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def set_facebooker_plus_options(options = {}) @facebooker_plus_options = options end def apply_facebooker_options(options = {}) if @facebooker_plus_options.has_key?(:app_class) then end end def create_session_cookie_if_needed # magic happens here end module ClassMethods def init_facebooker_plus(options = {}) before_filter { |controller| controller.set_facebooker_plus_options(options) } before_filter :create_session_cookie_if_needed before_filter :apply_facebooker_options end end end end end
  15. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def set_facebooker_plus_options(options = {}) @facebooker_plus_options = options end def apply_facebooker_options(options = {}) if @facebooker_plus_options.has_key?(:app_class) then end end def create_session_cookie_if_needed # magic happens here end module ClassMethods def init_facebooker_plus(options = {}) before_filter { |controller| controller.set_facebooker_plus_options(options) } before_filter :create_session_cookie_if_needed before_filter :apply_facebooker_options end end end end end
  16. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods end def set_facebooker_plus_options(options = {}) @facebooker_plus_options = options end def apply_facebooker_options(options = {}) if @facebooker_plus_options.has_key?(:app_class) then end end def create_session_cookie_if_needed # magic happens here end module ClassMethods def init_facebooker_plus(options = {}) before_filter { |controller| controller.set_facebooker_plus_options(options) } before_filter :create_session_cookie_if_needed before_filter :apply_facebooker_options end end end end end
  17. Very useful in some scenarios when complex functionality is needed.

    New Relic’s RPM plug-in uses it to display application performance under http://localhost:3000/newrelic. Decently complex to setup.
  18. init.rb if defined? Rails if defined? Facebooker require 'facebooker_plus/facebooker_plus' require

    'facebooker_plus/rails/fb_sig_add' require 'facebooker_plus/rails/controller' require 'facebooker_plus/rails/helper' require 'facebooker_plus/extensions/action_controller' require 'facebooker_plus/extensions/action_view' require 'facebooker_plus/extensions/session' FacebookerPlus::Base.init(defined?(config) ? config : nil) else STDERR.puts "** [FacebookerPlus] ERROR: Please load Facebooker before Facebooker Plus.\n" end end
  19. init.rb if defined? Rails if defined? Facebooker require 'facebooker_plus/facebooker_plus' require

    'facebooker_plus/rails/fb_sig_add' require 'facebooker_plus/rails/controller' require 'facebooker_plus/rails/helper' require 'facebooker_plus/extensions/action_controller' require 'facebooker_plus/extensions/action_view' require 'facebooker_plus/extensions/session' FacebookerPlus::Base.init(defined?(config) ? config : nil) else STDERR.puts "** [FacebookerPlus] ERROR: Please load Facebooker before Facebooker Plus.\n" end end
  20. facebooker_plus.rb module FacebookerPlus class Base def self.init(rails_config) controller_path = File.join(facebooker_plus_root,

    'lib', 'facebooker_plus', 'rails', 'app', 'controllers') helper_path = File.join(facebooker_plus_root, 'lib', 'facebooker_plus', 'rails', 'app', 'helpers') $LOAD_PATH << controller_path $LOAD_PATH << helper_path if defined? ActiveSupport::Dependencies ActiveSupport::Dependencies.load_paths << controller_path ActiveSupport::Dependencies.load_paths << helper_path elsif defined? Dependencies.load_paths Dependencies.load_paths << controller_path Dependencies.load_paths << helper_path else to_stderr "ERROR: Rails version #{(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : ''} too old." return end if rails_config rails_config.controller_paths << controller_path else current_paths = ActionController::Routing.controller_paths if current_paths.nil? || current_paths.empty? to_stderr "WARNING: Unable to modify the routes in this version of Rails. Developer mode not available." end current_paths << controller_path end end # more code here end end
  21. facebooker_plus.rb controller_path = File.join(facebooker_plus_root, 'lib', 'facebooker_plus', 'rails', 'app', 'controllers') helper_path

    = File.join(facebooker_plus_root, 'lib', 'facebooker_plus', 'rails', 'app', 'helpers') $LOAD_PATH << controller_path $LOAD_PATH << helper_path
  22. facebooker_plus.rb if defined? ActiveSupport::Dependencies ActiveSupport::Dependencies.load_paths << controller_path ActiveSupport::Dependencies.load_paths << helper_path

    elsif defined? Dependencies.load_paths Dependencies.load_paths << controller_path Dependencies.load_paths << helper_path else to_stderr "ERROR: Rails version #{(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : ''} too old." return end
  23. facebooker_plus.rb if rails_config rails_config.controller_paths << controller_path else current_paths = ActionController::Routing.controller_paths

    if current_paths.nil? || current_paths.empty? to_stderr "WARNING: Unable to modify the routes in this version of Rails. " + "Developer mode not available." end current_paths << controller_path end
  24. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods view_path = File.join(File.dirname(__FILE__), "app", "views") if controller.public_methods.include?("append_view_path") # rails 2.1+ controller.append_view_path(view_path) elsif controller.public_methods.include?("view_paths") # rails 2.0+ controller.view_paths << view_path else # rails <2.0 controller.template_root = view_path end end end end end
  25. controller.rb module FacebookerPlus module Rails module Controller def self.included(controller) controller.extend

    ClassMethods view_path = File.join(File.dirname(__FILE__), "app", "views") if controller.public_methods.include?("append_view_path") # rails 2.1+ controller.append_view_path(view_path) elsif controller.public_methods.include?("view_paths") # rails 2.0+ controller.view_paths << view_path else # rails <2.0 controller.template_root = view_path end end end end end
  26. Collecta_ruby source: http://github.com/jimeh/collecta_ruby Facebooker Plus source: http://github.com/jimeh/facebooker_plus Railscasts: Making a

    Plug-in: http://railscasts.com/episodes/33-making-a-plugin email: [email protected] — twitter: @jimeh slideshare: http://www.slideshare.net/jimeh