Last active
May 6, 2025 00:21
Revisions
-
endymion revised this gist
Jun 20, 2013 . 5 changed files with 177 additions and 84 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,8 +3,10 @@ class Contact < ActiveRecord::Base ... def after_create if Hook.hooks_exist?('new_contact', self) Resque.enqueue(Hook, self.class.name, self.id) # To trigger directly without Resque: Hook.trigger('new_contact', self) end end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,37 @@ class ContactTest < ActiveSupport::TestCase ... require 'fakeweb' def test_trigger_rest_hook_on_contact_creation account = Factory(:account) subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" hook = Hook.create( { "event" => "new_contact", "account_id" => account.id, "subscription_url" => subscription_url, "target_url" => target_url } ) FakeWeb.register_uri( :post, target_url, :body => 'irrelevant', :status => ['200', 'Triggered'] ) contact = Factory(:contact, :account => account, :first => 'Ryan', :last => 'Porter', :email => 'xyz@venuedriver.com') # Simulate a Resque worker. Hook.perform('Contact', contact.id) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(contact), FakeWeb.last_request.body end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,22 +1,27 @@ require 'resque-retry' class Hook < ActiveRecord::Base attr_accessible :event, :account_id, :subscription_url, :target_url validates_presence_of :event, :account_id, :subscription_url, :target_url # Looks for an appropriate REST hook that matches the record, and triggers the hook if one exists. def self.trigger(event, record) hooks = self.hooks(event, record) return if hooks.empty? unless Rails.env.development? # Trigger each hook if there is more than one for an account, which can happen. hooks.each do |hook| # These use puts instead of Rails.logger.info because this happens from a Resque worker. puts "Triggering REST hook: #{hook.inspect}" puts "REST hook event: #{event}" encoded_record = HookEncoder.encode(record) puts "REST hook record: #{encoded_record}" RestClient.post(hook.target_url, encoded_record) do |response, request, result| if response.code.eql? 410 puts "Destroying REST hook because of 410 response: #{hook.inspect}" hook.destroy end end end end @@ -25,10 +30,28 @@ def self.trigger(event, record) # This method is called by a Resque worker. Resque stores the record's class and ID, and the # Resque worker provides those values as parameters to this method. def self.perform(klass, id) # puts "Performing REST hook Resque job: #{klass} #{id}" event = "new_#{klass.to_s.underscore}" record = klass.camelize.constantize.find(id) Hook.trigger(event, record) end @queue = :rest_hook extend Resque::Plugins::Retry @retry_limit = 3 @retry_delay = 5 # Returns all hooks for a given event and account. def self.hooks(event, record) Hook.find(:all, :conditions => { :event => event, :account_id => record.account_id, }) end # Tests whether any hooks exist for a given event and account, for deciding whether or not to # enqueue Resque jobs. def self.hooks_exist?(event, record) self.hooks(event, record).size > 0 end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -14,7 +14,7 @@ def setup @contact = Factory(:contact, :account => @account, :first => 'Ryan', :last => 'Porter', :email => 'xyz@venuedriver.com') @hook = Hook.create( { "event" => "new_contact", "account_id" => @account.id, @@ -24,6 +24,33 @@ def setup ) end def test_hooks_exist assert_equal true, Hook.hooks_exist?('new_contact', @contact) Hook.destroy_all assert_equal false, Hook.hooks_exist?('new_contact', @contact) end def test_hooks hooks = Hook.hooks('new_contact', @contact) assert_equal 1, hooks.size assert_equal @hook, hooks.first second_hook = Hook.create( { "event" => "new_contact", "account_id" => @account.id, "subscription_url" => @subscription_url, "target_url" => @target_url } ) hooks = Hook.hooks('new_contact', @contact) assert_equal 2, hooks.size assert_equal second_hook, hooks.last end def test_trigger FakeWeb.register_uri( :post, @@ -36,7 +63,7 @@ def test_trigger Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end @@ -52,7 +79,7 @@ def test_trigger_remove_hook_on_410_response Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 0, Hook.count # The 410 response should trigger removal of the hook. end @@ -71,7 +98,7 @@ def test_resque_background_job Hook.perform(Contact.name, @contact.id) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,101 +1,105 @@ require 'test_helper' require 'fakeweb' class HookTest < ActiveSupport::TestCase def setup @account = Factory(:account) @subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" @target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" # Create the record before the hook is created or else the after_create Active Model hook # will trigger the REST hook prematurely during testing. @contact = Factory(:contact, :account => @account, :first => 'Ryan', :last => 'Porter', :email => 'xyz@venuedriver.com') @hook = Hook.create( { "event" => "new_contact", "account_id" => @account.id, "subscription_url" => @subscription_url, "target_url" => @target_url } ) end def test_hooks_exist assert_equal true, Hook.hooks_exist?('new_contact', @contact) Hook.destroy_all assert_equal false, Hook.hooks_exist?('new_contact', @contact) end def test_hooks hooks = Hook.hooks('new_contact', @contact) assert_equal 1, hooks.size assert_equal @hook, hooks.first second_hook = Hook.create( { "event" => "new_contact", "account_id" => @account.id, "subscription_url" => @subscription_url, "target_url" => @target_url } ) hooks = Hook.hooks('new_contact', @contact) assert_equal 2, hooks.size assert_equal second_hook, hooks.last end def test_trigger FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['200', 'Triggered'] ) FakeWeb.allow_net_connect = false Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end def test_trigger_remove_hook_on_410_response FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['410', 'Danger, Will Robinson!'] ) FakeWeb.allow_net_connect = false Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 0, Hook.count # The 410 response should trigger removal of the hook. end def test_resque_background_job FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['200', 'Triggered'] ) FakeWeb.allow_net_connect = false # A Resque worker will normally do this, which should have the same effect as when the hook # is manually triggered in the test_trigger test. Hook.perform(Contact.name, @contact.id) assert_equal "POST", FakeWeb.last_request.method assert_equal HookEncoder.encode(@contact), FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end end -
endymion revised this gist
Jun 18, 2013 . 1 changed file with 10 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,10 @@ class Contact < ActiveRecord::Base ... def after_create Resque.enqueue(Hook, self.class.name, self.id) # To trigger directly without Resque: Hook.trigger('new_contact', self) end end -
endymion revised this gist
Jun 18, 2013 . 2 changed files with 35 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -22,4 +22,13 @@ def self.trigger(event, record) end end # This method is called by a Resque worker. Resque stores the record's class and ID, and the # Resque worker provides those values as parameters to this method. def self.perform(klass, id) event = "new_#{klass.to_s.downcase}" record = klass.camelize.constantize.find(id) Hook.trigger(event, record) end @queue = :rest_hook end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -9,6 +9,11 @@ def setup @subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" @target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" # Create the record before the hook is created or else the after_create Active Model hook # will trigger the REST hook prematurely during testing. @contact = Factory(:contact, :account => @account, :first => 'Ryan', :last => 'Porter', :email => 'xyz@venuedriver.com') hook = Hook.create( { "event" => "new_contact", @@ -17,9 +22,6 @@ def setup "target_url" => @target_url } ) end def test_trigger @@ -29,11 +31,13 @@ def test_trigger :body => 'irrelevant', :status => ['200', 'Triggered'] ) FakeWeb.allow_net_connect = false Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal @contact.to_json, FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end def test_trigger_remove_hook_on_410_response @@ -43,6 +47,7 @@ def test_trigger_remove_hook_on_410_response :body => 'irrelevant', :status => ['410', 'Danger, Will Robinson!'] ) FakeWeb.allow_net_connect = false Hook.trigger('new_contact', @contact) @@ -52,4 +57,22 @@ def test_trigger_remove_hook_on_410_response assert_equal 0, Hook.count # The 410 response should trigger removal of the hook. end def test_resque_background_job FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['200', 'Triggered'] ) FakeWeb.allow_net_connect = false # A Resque worker will normally do this, which should have the same effect as when the hook # is manually triggered in the test_trigger test. Hook.perform(Contact.name, @contact.id) assert_equal "POST", FakeWeb.last_request.method assert_equal @contact.to_json, FakeWeb.last_request.body assert_equal 1, Hook.count # The hook should not have been deleted. end end -
endymion created this gist
Jun 18, 2013 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,25 @@ class Hook < ActiveRecord::Base attr_accessible :event, :account_id, :subscription_url, :target_url validates_presence_of :event, :account_id, :subscription_url, :target_url # Looks for an appropriate REST hook that matches the record, and triggers the hook if one exists. def self.trigger(event, record) hooks = Hook.find(:all, :conditions => { :event => event, :account_id => record.account_id, }) return if hooks.empty? unless Rails.env.development? hook = hooks.first Rails.logger.info "Triggering REST hook: #{hook.inspect}" RestClient.post(hook.target_url, record.to_json) do |response, request, result| if response.code.eql? 410 Rails.logger.info "Destroying REST hook because of 410 response: #{hook.inspect}" hook.destroy end end end end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,55 @@ require 'test_helper' require 'fakeweb' class HookTest < ActiveSupport::TestCase def setup @account = Factory(:account) @subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" @target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" hook = Hook.create( { "event" => "new_contact", "account_id" => @account.id, "subscription_url" => @subscription_url, "target_url" => @target_url } ) @contact = Factory(:contact, :account => @account, :first => 'Ryan', :last => 'Porter', :email => 'xyz@venuedriver.com') end def test_trigger FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['200', 'Triggered'] ) Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal @contact.to_json, FakeWeb.last_request.body end def test_trigger_remove_hook_on_410_response FakeWeb.register_uri( :post, @target_url, :body => 'irrelevant', :status => ['410', 'Danger, Will Robinson!'] ) Hook.trigger('new_contact', @contact) assert_equal "POST", FakeWeb.last_request.method assert_equal @contact.to_json, FakeWeb.last_request.body assert_equal 0, Hook.count # The 410 response should trigger removal of the hook. end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,19 @@ class HooksController < ApplicationController def create hook = Hook.new params render :nothing => true, :status => 500 and return unless hook.save Rails.logger.info "Created REST hook: #{hook.inspect}" # The Zapier documentation says to return 201 - Created. render :json => hook.to_json(:only => :id), :status => 201 end def destroy hook = Hook.find(params[:id]) if params[:id] if hook.nil? && params[:subscription_url] hook = Hook.find_by_subscription_url(params[:subscription_url]).destroy end Rails.logger.info "Destroying REST hook: #{hook.inspect}" hook.destroy render :nothing => true, :status => 200 end end This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,101 @@ require 'test_helper' class HooksControllerTest < ActionController::TestCase def setup @account = Factory(:account) @user = Factory(:user, :username => 'username') # Password: "password", by default. @staff = Factory(:staff, :account => @account, :user => @user) end def test_subscribe_requires_authentication post :create, { "event" => "new_contact", "subscription_url" => "whatever", "target_url" => "whatever" } assert_redirected_to :controller => 'public/signin', :action => 'signin' end def test_subscribe @request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password") subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" post :create, { "event" => "new_contact", "account_id" => @account.id, "subscription_url" => subscription_url, "target_url" => target_url } assert_response 201 assert_equal 1, Hook.count hook = Hook.last assert_equal %Q[{"id":#{hook.id}}], @response.body assert_equal "new_contact", hook.event assert_equal @account.id, hook.account_id.to_i assert_equal subscription_url, hook.subscription_url assert_equal target_url, hook.target_url end def test_subscribe_error @request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password") post :create, nil assert_response 500 assert_equal 0, Hook.count end def test_unsubscribe_rest @request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password") hook = Hook.create( :event => "new_contact", :account_id => @account.id, :subscription_url => 'whatever', :target_url => 'whatever' ) assert_equal 1, Hook.count post :destroy, { :id => hook.id.to_s, :subscription_url => 'whatever' } assert_response 200 assert_equal 0, Hook.count end def test_unsubscribe_the_hacky_way @request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password") subscription_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" target_url = "https://zapier.com/hooks/standard/wpGRPPcRxZt2GxBbSSeUAlWPBnhLiRWB/" Hook.create( :event => "new_contact", :account_id => @account.id, :subscription_url => subscription_url, :target_url => target_url ) assert_equal 1, Hook.count post :destroy, { "subscription_url" => subscription_url } assert_response 200 assert_equal 0, Hook.count end end