Rails Plugin Development 101 (...and some...)

46
Ruby on Rails: Plug-in Development 101 ...and some... by Jim Myhrberg http://jimeh.me /

description

A basic introduction of Ruby on Rails plug-in development which goes on to more advanced plug-in development.

Transcript of Rails Plugin Development 101 (...and some...)

Page 1: Rails Plugin Development 101 (...and some...)

Ruby on Rails:Plug-in Development 101...and some...

by Jim Myhrberghttp://jimeh.me/

Page 2: Rails Plugin Development 101 (...and some...)

The Basics

Helpers

Controller Methods

Controllers, Helpers & Views

Page 3: Rails Plugin Development 101 (...and some...)

to get you started

The Basics

Page 4: Rails Plugin Development 101 (...and some...)

Generate a Plug-in$ script/generate plugin hello_world

Page 5: Rails Plugin Development 101 (...and some...)

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.

Page 6: Rails Plugin Development 101 (...and some...)

Example: Collecta_ruby

Page 7: Rails Plugin Development 101 (...and some...)

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

Page 8: Rails Plugin Development 101 (...and some...)

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) endend

Page 9: Rails Plugin Development 101 (...and some...)

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 endend

Page 10: Rails Plugin Development 101 (...and some...)

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

Page 11: Rails Plugin Development 101 (...and some...)

Example: Facebooker Plus

Page 12: Rails Plugin Development 101 (...and some...)

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

Page 13: Rails Plugin Development 101 (...and some...)

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" endend

Page 14: Rails Plugin Development 101 (...and some...)

create or overload helpers

Helpers

Page 15: Rails Plugin Development 101 (...and some...)

One of the simplest things to implement in a Plug-in.

Page 16: Rails Plugin Development 101 (...and some...)

action_view.rb

Page 17: Rails Plugin Development 101 (...and some...)

action_view.rb

ActionView::Base.send(:include, FacebookerPlus::Rails::Helper)

Page 18: Rails Plugin Development 101 (...and some...)

helper.rb

Page 19: Rails Plugin Development 101 (...and some...)

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 endend

Page 20: Rails Plugin Development 101 (...and some...)

access custom methods in all controllers

Controller Methods

Page 21: Rails Plugin Development 101 (...and some...)

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.

Page 22: Rails Plugin Development 101 (...and some...)

action_controller.rb

Page 23: Rails Plugin Development 101 (...and some...)

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 endend

Page 24: Rails Plugin Development 101 (...and some...)

controller.rb

Page 25: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 26: Rails Plugin Development 101 (...and some...)

Fancy initialization

Page 27: Rails Plugin Development 101 (...and some...)

application_controller.rb

class ApplicationController < ActionController::Base

init_facebooker_plus(:app_class => "App")

end

Page 28: Rails Plugin Development 101 (...and some...)

application_controller.rb

class ApplicationController < ActionController::Base

init_facebooker_plus(:app_class => "App")

end

Page 29: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 30: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 31: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 32: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 33: Rails Plugin Development 101 (...and some...)

controller.rbmodule 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 endend

Page 34: Rails Plugin Development 101 (...and some...)

full controllers, helpers and views in your plug-in

Controllers, Helpers & Views

Page 35: Rails Plugin Development 101 (...and some...)

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.

Page 36: Rails Plugin Development 101 (...and some...)

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" endend

Page 37: Rails Plugin Development 101 (...and some...)

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" endend

Page 38: Rails Plugin Development 101 (...and some...)

facebooker_plus.rb

Page 39: Rails Plugin Development 101 (...and some...)

facebooker_plus.rbmodule 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 endend

Page 40: Rails Plugin Development 101 (...and some...)

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

Page 41: Rails Plugin Development 101 (...and some...)

facebooker_plus.rb

if defined? ActiveSupport::Dependencies ActiveSupport::Dependencies.load_paths << controller_path ActiveSupport::Dependencies.load_paths << helper_pathelsif defined? Dependencies.load_paths Dependencies.load_paths << controller_path Dependencies.load_paths << helper_pathelse to_stderr "ERROR: Rails version #{(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : ''} too old." returnend

Page 42: Rails Plugin Development 101 (...and some...)

facebooker_plus.rb

if rails_config rails_config.controller_paths << controller_pathelse 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_pathend

Page 43: Rails Plugin Development 101 (...and some...)

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 endend

Page 44: Rails Plugin Development 101 (...and some...)

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 endend

Page 45: Rails Plugin Development 101 (...and some...)

The Plug-in “app” folder

Page 46: Rails Plugin Development 101 (...and some...)

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