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

Post on 08-May-2015

1.433 views 0 download

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

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

by Jim Myhrberghttp://jimeh.me/

The Basics

Helpers

Controller Methods

Controllers, Helpers & Views

to get you started

The Basics

Generate a Plug-in$ script/generate plugin hello_world

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.

Example: Collecta_ruby

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

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

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

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

Example: Facebooker Plus

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

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

create or overload helpers

Helpers

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

action_view.rb

action_view.rb

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

helper.rb

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

access custom methods in all controllers

Controller Methods

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.

action_controller.rb

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

controller.rb

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

Fancy initialization

application_controller.rb

class ApplicationController < ActionController::Base

init_facebooker_plus(:app_class => "App")

end

application_controller.rb

class ApplicationController < ActionController::Base

init_facebooker_plus(:app_class => "App")

end

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

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

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

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

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

full controllers, helpers and views in your plug-in

Controllers, Helpers & Views

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.

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

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

facebooker_plus.rb

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

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

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

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

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

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

The Plug-in “app” folder

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: contact@jimeh.me — twitter: @jimeh slideshare: http://www.slideshare.net/jimeh