conduit 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +22 -0
  3. data/app/controllers/conduit/responses_controller.rb +14 -0
  4. data/app/models/conduit/concerns/storage.rb +41 -0
  5. data/app/models/conduit/request.rb +109 -0
  6. data/app/models/conduit/response.rb +50 -0
  7. data/app/models/conduit/subscription.rb +6 -0
  8. data/config/routes.rb +3 -0
  9. data/db/migrate/20131209223122_create_conduit_requests.rb +13 -0
  10. data/db/migrate/20131209223734_create_conduit_responses.rb +10 -0
  11. data/db/migrate/20140305202729_create_conduit_subscriptions.rb +12 -0
  12. data/lib/conduit.rb +60 -0
  13. data/lib/conduit/acts_as_conduit_subscriber.rb +41 -0
  14. data/lib/conduit/configuration.rb +43 -0
  15. data/lib/conduit/core/action.rb +136 -0
  16. data/lib/conduit/core/connection.rb +85 -0
  17. data/lib/conduit/core/driver.rb +83 -0
  18. data/lib/conduit/core/parser.rb +55 -0
  19. data/lib/conduit/core/render.rb +69 -0
  20. data/lib/conduit/drivers/keep +0 -0
  21. data/lib/conduit/engine.rb +20 -0
  22. data/lib/conduit/storage.rb +37 -0
  23. data/lib/conduit/storage/aws.rb +94 -0
  24. data/lib/conduit/storage/file.rb +74 -0
  25. data/lib/conduit/util.rb +17 -0
  26. data/lib/conduit/version.rb +3 -0
  27. data/lib/tasks/conduit_tasks.rake +4 -0
  28. data/spec/classes/acts_as_conduit_subscriber_spec.rb +61 -0
  29. data/spec/classes/core/action_spec.rb +53 -0
  30. data/spec/classes/core/driver_spec.rb +24 -0
  31. data/spec/controllers/responses_controller_spec.rb +29 -0
  32. data/spec/dummy/README.rdoc +28 -0
  33. data/spec/dummy/Rakefile +6 -0
  34. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  35. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  38. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  39. data/spec/dummy/bin/bundle +3 -0
  40. data/spec/dummy/bin/rails +4 -0
  41. data/spec/dummy/bin/rake +4 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/config/application.rb +29 -0
  44. data/spec/dummy/config/boot.rb +5 -0
  45. data/spec/dummy/config/database.yml +20 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +29 -0
  48. data/spec/dummy/config/environments/production.rb +80 -0
  49. data/spec/dummy/config/environments/test.rb +36 -0
  50. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/dummy/config/initializers/conduit.rb +5 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  55. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  56. data/spec/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec/dummy/config/locales/en.yml +23 -0
  59. data/spec/dummy/config/routes.rb +3 -0
  60. data/spec/dummy/db/schema.rb +46 -0
  61. data/spec/dummy/lib/conduit/drivers/my_driver/actions/foo.rb +10 -0
  62. data/spec/dummy/lib/conduit/drivers/my_driver/driver.rb +7 -0
  63. data/spec/dummy/lib/conduit/drivers/my_driver/parsers/foo.rb +13 -0
  64. data/spec/dummy/lib/conduit/drivers/my_driver/views/foo.erb +5 -0
  65. data/spec/dummy/lib/conduit/drivers/my_driver/views/layout.erb +3 -0
  66. data/spec/dummy/log/development.log +0 -0
  67. data/spec/dummy/log/test.log +9707 -0
  68. data/spec/dummy/public/404.html +58 -0
  69. data/spec/dummy/public/422.html +58 -0
  70. data/spec/dummy/public/500.html +57 -0
  71. data/spec/dummy/tmp/conduit/1/my_driver/foo/1-response.xml +3 -0
  72. data/spec/dummy/tmp/conduit/1/my_driver/foo/request.xml +7 -0
  73. data/spec/models/conduit/request_spec.rb +50 -0
  74. data/spec/models/conduit/response_spec.rb +52 -0
  75. data/spec/spec_helper.rb +36 -0
  76. data/spec/support/helper.rb +15 -0
  77. data/spec/support/xml/xml_request.xml +7 -0
  78. data/spec/support/xml/xml_response.xml +3 -0
  79. metadata +311 -0
@@ -0,0 +1,136 @@
1
+ #
2
+ # The job of this class is to provide common
3
+ # functionality to a action class.
4
+ #
5
+ # e.g.
6
+ # => class MyAction < Conduit::Core::Action
7
+ # => required_attributes :foo, :bar
8
+ # => optional_attributes :baz
9
+ # => end
10
+ #
11
+ # => action = MyAction.new(foo: 'foo', bar: 'bar', baz: 'baz')
12
+ # => action.perform
13
+ #
14
+
15
+ require 'set'
16
+
17
+ module Conduit
18
+ module Core
19
+ class Action
20
+
21
+ def self.inherited(base)
22
+ base.send :include, Conduit::Core::Connection
23
+ base.send :include, Conduit::Core::Render
24
+ base.send :include, InstanceMethods
25
+ base.extend ClassMethods
26
+
27
+ # TODO: Move this to the driver scope
28
+ # which allows for setting this
29
+ # "globally" for the driver.
30
+ #
31
+ path = caller.first[/^[^:]+/]
32
+ define_method(:action_path) do
33
+ File.dirname(path)
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ attr_accessor :_action_path
40
+
41
+ # Set required attributes
42
+ #
43
+ # e.g.
44
+ # => required_attributes :foo, :bar, :baz
45
+ #
46
+ def required_attributes(*args)
47
+ requirements.merge(args)
48
+ attributes.merge(args)
49
+ end
50
+
51
+ # Set optional attributes
52
+ #
53
+ # e.g.
54
+ # => optional_attributes :foo, :bar, :baz
55
+ #
56
+ def optional_attributes(*args)
57
+ attributes.merge(args)
58
+ end
59
+
60
+ # Storage array for required attributes
61
+ #
62
+ def requirements
63
+ @requirements ||= Set.new
64
+ end
65
+
66
+ # Storage array for all attributes
67
+ #
68
+ def attributes
69
+ @attributes ||= Set.new
70
+ end
71
+
72
+ end
73
+
74
+ module InstanceMethods
75
+
76
+ delegate :requirements, :attributes, to: :class
77
+
78
+ def initialize(**options)
79
+ @options = options
80
+ validate!(options)
81
+ end
82
+
83
+ # Object used for passing data to the view
84
+ # Only keys listed in attributes will be
85
+ # used.
86
+ #
87
+ def view_context
88
+ OpenStruct.new(@options.select do |k,v|
89
+ attributes.include?(k)
90
+ end)
91
+ end
92
+
93
+ # Location where the view files can be found
94
+ # Default to lib/conduit/drivers/<drivername>/views
95
+ # Can be overriden per class.
96
+ #
97
+ def view_path
98
+ File.join(File.dirname(action_path), 'views')
99
+ end
100
+
101
+ # Return the rendered view
102
+ #
103
+ def view
104
+ tpl = self.class.name.demodulize
105
+ .underscore.downcase
106
+ render(tpl)
107
+ end
108
+
109
+ # Entry method. The class will use
110
+ # use this to trigger the request.
111
+ #
112
+ # Override to customize.
113
+ #
114
+ def perform
115
+ request(body: view, method: :post)
116
+ end
117
+
118
+ private
119
+
120
+ # Ensures that all required attributes are present
121
+ # If not all attributes are present, will raise
122
+ # an ArgumentError listing missing attributes
123
+ #
124
+ def validate!(options)
125
+ missing_keys = (requirements.to_a - options.keys)
126
+ if missing_keys.any?
127
+ raise ArgumentError,
128
+ "Missing keys: #{missing_keys.join(', ')}"
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,85 @@
1
+ #
2
+ # The job of this module is to handle network
3
+ # communication rom Conduit to a remote host.
4
+ #
5
+
6
+ require 'conduit/version'
7
+ require 'excon'
8
+
9
+ module Conduit
10
+ module Core
11
+ module Connection
12
+
13
+ def self.included(base)
14
+ base.extend ClassMethods
15
+ base.send :include, InstanceMethods
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ # Define a remote_url
21
+ #
22
+ # e.g.
23
+ # remote_url 'http://myapi.com/endpoint'
24
+ #
25
+ def remote_url(host=nil)
26
+ @remote_url ||= host
27
+ end
28
+
29
+ end
30
+
31
+ module InstanceMethods
32
+
33
+ delegate :remote_url, to: :class
34
+
35
+ # Make a request
36
+ #
37
+ # @param [Hash] params
38
+ # @option params [String] :body text to be sent over a socket
39
+ # @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
40
+ # @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
41
+ # @option params [String] :path appears after 'scheme://host:port/'
42
+ # @option params [Fixnum] :port The port on which to connect, to the destination host
43
+ # @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
44
+ # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
45
+ # @option params [Proc] :response_block
46
+ #
47
+ #
48
+ def request(params, &block)
49
+ params[:headers] ||= {}
50
+ params[:headers]['User-Agent'] ||= "conduit/#{Conduit::VERSION}"
51
+ connection.request(params, &block)
52
+ end
53
+
54
+ private
55
+
56
+ # Connection that will be used
57
+ #
58
+ # @param [Hash] params
59
+ # @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
60
+ # @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
61
+ # @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
62
+ # @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
63
+ # @option params [Fixnum] :port The port on which to connect, to the destination host
64
+ # @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
65
+ # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
66
+ # @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
67
+ # @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
68
+ # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
69
+ # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
70
+ #
71
+ # @return [Excon::Response]
72
+ #
73
+ # @raise [Excon::Errors::StubNotFound]
74
+ # @raise [Excon::Errors::Timeout]
75
+ # @raise [Excon::Errors::SocketError]
76
+ #
77
+ def connection(**params)
78
+ @excon ||= Excon.new(remote_url, params)
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,83 @@
1
+ #
2
+ # The job of this class is to require all
3
+ # actions belonging to this driver
4
+ #
5
+ # e.g.
6
+ # => module Conduit::Driver
7
+ # => class MyDriver < Conduit::Core::Driver
8
+ # => required_credentials :foo, :bar, :baz
9
+ # =>
10
+ # => action :purchase
11
+ # => action :activate
12
+ # => action :suspend
13
+ # =>
14
+ # => end
15
+ # => end
16
+ #
17
+
18
+ require 'set'
19
+
20
+ module Conduit
21
+ module Core
22
+ module Driver
23
+
24
+ def self.extended(base)
25
+ base.instance_variable_set("@_driver_path",
26
+ File.dirname(caller.first[/^[^:]+/]))
27
+ end
28
+
29
+ # Set required credentials
30
+ #
31
+ # e.g.
32
+ # => required_credentials :foo, :bar, :baz
33
+ #
34
+ def required_credentials(*args)
35
+ credentials.merge(args)
36
+ end
37
+
38
+ # Set available actions
39
+ #
40
+ # e.g.
41
+ # => action :purchase
42
+ #
43
+ def action(action_name)
44
+ require File.join(@_driver_path, 'actions', action_name.to_s)
45
+ require File.join(@_driver_path, 'parsers', action_name.to_s)
46
+ actions << action_name
47
+ end
48
+
49
+ # Storage array for required credentials
50
+ #
51
+ # e.g.
52
+ # Conduit::Driver::Fusion.credentials
53
+ # => [:foo, :bar, :baz]
54
+ #
55
+ def credentials
56
+ @credentials ||= Set.new
57
+ end
58
+
59
+ # Storage array for required credentials
60
+ #
61
+ # e.g.
62
+ # Conduit::Driver::Fusion.actions
63
+ # => [:purchase]
64
+ #
65
+ def actions
66
+ @actions ||= Set.new
67
+ end
68
+
69
+ private
70
+
71
+ # Return the name of the driver
72
+ #
73
+ # e.g.
74
+ # Conduit::Drivers::Fusion.name
75
+ # => "fusion"
76
+ #
77
+ def driver_name
78
+ self.name.demodulize.underscore.downcase
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ #
2
+ # The job of this class is parse a raw response
3
+ # and provide parsed attributes that can be
4
+ # predictably consumed.
5
+ #
6
+ module Conduit
7
+ module Core
8
+ class Parser
9
+
10
+ class << self
11
+
12
+ # Define an attribute that will be publically exposed
13
+ # when dealing with conduit responses.
14
+ #
15
+ def attribute(attr_name, &block)
16
+ attributes << attr_name
17
+ define_method(attr_name, &block)
18
+ end
19
+
20
+ # Storage array for all attributes
21
+ #
22
+ def attributes
23
+ @attributes ||= []
24
+ end
25
+
26
+ end
27
+
28
+ delegate :attributes, to: :class
29
+
30
+ # Returns a hash representation of each attribute
31
+ # defined in a parser and its value.
32
+ #
33
+ def serializable_hash
34
+ attributes.inject({}) do |hash, attribute|
35
+ hash.tap { |h| h[attribute] = send(attribute) }
36
+ end
37
+ end
38
+
39
+ # Default response status.
40
+ # Should be overwritten by parser implementation.
41
+ #
42
+ def response_status
43
+ raise NoMethodError, "Please define response_status in your parser."
44
+ end
45
+
46
+ # Default response error container.
47
+ # Should be overwritten by parser implementation.
48
+ #
49
+ def response_errors
50
+ raise NoMethodError, "Please define response_errors in your parser."
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ #
2
+ # The job of this module is to provide template
3
+ # rendering functionality. Included classes
4
+ # must provide a view_path, and view_context
5
+ # methods to be of any use.
6
+ #
7
+
8
+ require 'tilt'
9
+
10
+ module Conduit
11
+ module Core
12
+ module Render
13
+
14
+ # Create instance variables, any of these can
15
+ # be overriden within the including class.
16
+ #
17
+ # view_path: Location where the view files are stored
18
+ # view_context: Object that contains the variables used in the template
19
+ #
20
+ def self.included(base)
21
+ attr_accessor :view_path, :view_context
22
+ end
23
+
24
+ # Render a template file
25
+ #
26
+ # e.g. Without layout
27
+ # => render :purchase, layout: false
28
+ #
29
+ # e.g. With layout
30
+ # => render :purchase
31
+ #
32
+ def render(file, layout: true)
33
+ raise ViewPathNotDefined, "" unless view_path
34
+ layout ? render_with_layout(file) : render_template(file)
35
+ end
36
+
37
+ # Render a template file
38
+ #
39
+ # e.g. Without layout
40
+ # => render_template(:template)
41
+ #
42
+ # e.g. With layout
43
+ # => render_template(:layout) do
44
+ # => render_template(:template)
45
+ # => end
46
+ #
47
+ def render_template(file)
48
+ path = File.join(view_path, "#{file}.erb")
49
+ Tilt::ERBTemplate.new(path).render(view_context) do
50
+ yield if block_given?
51
+ end
52
+ end
53
+
54
+ # Render the file with a layout
55
+ #
56
+ # e.g.
57
+ # => render_layout(:template)
58
+ #
59
+ def render_with_layout(file)
60
+ render_template(:layout) do
61
+ render_template(file)
62
+ end
63
+ end
64
+
65
+ class ViewPathNotDefined < StandardError; end
66
+
67
+ end
68
+ end
69
+ end
File without changes
@@ -0,0 +1,20 @@
1
+ module Conduit
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Conduit
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec, :fixture => false
7
+ g.assets false
8
+ g.helper false
9
+ end
10
+
11
+ initializer "conduit.load_app_root", before: :load_config_initializers do |app|
12
+ Conduit.app_root = app.root
13
+ end
14
+
15
+ initializer 'conduit.load_drivers', after: :load_config_initializers do |app|
16
+ Conduit::Driver.load_drivers
17
+ end
18
+
19
+ end
20
+ end