consumerable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c9d5f8170c3d34ec7da8bf6f91ab1cc6bcf4d640
4
+ data.tar.gz: f233298e249dec73caab12f519e432c37fe39ad8
5
+ SHA512:
6
+ metadata.gz: 39782f49579e362ec5b921723367c63f00099dfbddda482421b7ca86d2c8bb3b9a7b47d5ec34e004936f23cb719e9856a6b7a4d5961c5cf0227f7de50d2c308b
7
+ data.tar.gz: 81d6a03e63e7337722d3ac282299a30a1f8246b18a30f92e9c66f0d70ae935d4c36e50f597ffb315747a61e0da8287009e8039d7a67879051ca6487ddb41cb31
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in consumerable.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matt Beedle
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,70 @@
1
+ # Consumerable
2
+
3
+ Quickly write API object mapper libraries without having to re-invent the wheel
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'consumerable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install consumerable
18
+
19
+ ## Usage
20
+
21
+ First configure consumerable for the REST API that you would like to consume
22
+ ```ruby
23
+ Consumerable.configure do |config|
24
+ config.endpoint = 'https://api.heroku.com'
25
+ config.content_type = :json
26
+ config.accept_header = 'application/vnd.heroku+json; version=3'
27
+ config.basic_auth_username = ENV['HEROKU_USERNAME']
28
+ config.basic_auth_password = ENV['HEROKU_PASSWORD']
29
+ config.logger = my_logger
30
+ end
31
+ ```
32
+
33
+ Then in your gem/application create the classes to match the API
34
+ ```ruby
35
+ class HerokuPlatform::App
36
+ include Consumerable
37
+
38
+ attribute :name, String
39
+
40
+ consumerable path: '/apps'
41
+
42
+ has_many :collaborators, class_name: 'HerokuPlatform::Collaborators',
43
+ source: :app
44
+ end
45
+
46
+ class HerokuPlatform::Collaborator
47
+ include Consumerable
48
+
49
+ attribute :email, String
50
+
51
+ consumerable path: "/apps/:app_id/collaborators"
52
+
53
+ belongs_to :app, class_name: 'HerokuPlatform::App'
54
+ end
55
+ ```
56
+
57
+ Profit
58
+
59
+ ```ruby
60
+ app = HerokuPlatform::App.create!(name: 'some app')
61
+ app.collaborators.create(email: 'mattbeedle@gmail.com')
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'consumerable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'consumerable'
8
+ spec.version = Consumerable::VERSION
9
+ spec.authors = ['Matt Beedle']
10
+ spec.email = ['mattbeedle@googlemail.com']
11
+ spec.description = %q{Quickly write API object mappers without having to re-invent the wheel}
12
+ spec.summary = %q{Quickly write API object mappers without having to re-invent the wheel}
13
+ spec.homepage = 'http://www.github.com/mattbeedle/consumerable'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'activemodel'
22
+ spec.add_runtime_dependency 'activesupport'
23
+ spec.add_runtime_dependency 'faraday'
24
+ spec.add_runtime_dependency 'typhoeus'
25
+ spec.add_runtime_dependency 'virtus'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.3'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'rspec'
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'consumerable/version'
2
+
3
+ module Consumerable
4
+ extend ActiveSupport::Autoload
5
+
6
+ class << self
7
+ attr_accessor :configuration
8
+ end
9
+
10
+ def self.configure
11
+ self.configuration = Consumerable::Configuration.new
12
+ yield self.configuration
13
+ self.configuration
14
+ end
15
+
16
+ eager_autoload do
17
+ autoload :Associations
18
+ autoload :Configuration
19
+ autoload :Connection
20
+ autoload :Creatable
21
+ autoload :Deletable
22
+ autoload :Listable
23
+ autoload :Model
24
+ end
25
+
26
+ def self.eager_autoload!
27
+ super
28
+ Consumerable::Associations.eager_load!
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module Consumerable
2
+ module Associations
3
+ extend ActiveSupport::Autoload
4
+ extend ActiveSupport::Concern
5
+
6
+ autoload :BelongsTo
7
+ autoload :HasMany
8
+ autoload :HasManyProxy
9
+
10
+ included do
11
+ include Consumerable::Associations::BelongsTo
12
+ include Consumerable::Associations::HasMany
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ module Consumerable
2
+ module Associations
3
+ module BelongsTo
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def belongs_to(association_name, options = {})
8
+ foreign_key = options[:foreign_key] || :"#{association_name}_id"
9
+
10
+ class_eval do
11
+ attribute foreign_key, String
12
+ end
13
+
14
+ (class << self; self; end).instance_eval do
15
+ define_method "_for_#{association_name}" do |id|
16
+ init_collection Consumerable::Connection.
17
+ get(list_path.gsub(/:#{association_name}/, id))
18
+ end
19
+ end
20
+
21
+ define_method association_name do
22
+ instance_variable_get(:"@#{association_name}") ||
23
+ if self.send(foreign_key)
24
+ options[:class_name].constantize.
25
+ find(self.send(foreign_key)).tap do |parent|
26
+ self.send("#{association_name}=", object)
27
+ end
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ define_method "#{association_name}=" do |associated_object|
34
+ instance_variable_set(:"@#{association_name}", associated_object)
35
+ id = associated_object ? associated_object.id : nil
36
+ self.send "#{foreign_key}=", id
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ module Consumerable
2
+ module Associations
3
+ module HasMany
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_many(association_name, options = {})
8
+ define_method association_name do
9
+ instance_variable_get(:"@#{association_name}") ||
10
+ Consumerable::Associations::HasManyProxy.new(
11
+ self,
12
+ options[:class_name].constantize,
13
+ options[:class_name].constantize.
14
+ send("_for_#{self.class.to_s.demodulize.downcase}", self.id),
15
+ options[:source]
16
+ ).tap do |proxy|
17
+ instance_variable_set :"@#{association_name}", proxy
18
+ end
19
+ instance_variable_get :"@#{association_name}"
20
+ end
21
+
22
+ define_method "#{association_name}=" do |associated_objects|
23
+ if associated_objects.is_a?(Hash)
24
+ associated_objects = Array(
25
+ options[:class_name].constanize.new(
26
+ associated_objects[options[:class_name].demodulize.downcase]
27
+ )
28
+ )
29
+ end
30
+ instance_variable_set :"@#{association_name}",
31
+ Consumerable::Associations::HasManyProxy.new(
32
+ self, options[:class_name].constantize,
33
+ associated_objects, options[:source]
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Consumerable
2
+ module Associations
3
+ class HasManyProxy < BasicObject
4
+
5
+ attr_reader :parent, :target_klass, :target, :source
6
+
7
+ def initialize(parent, target_klass, target, source)
8
+ @parent = parent
9
+ @target_klass = target_klass
10
+ @target = target
11
+ @source = source
12
+ end
13
+
14
+ def method_missing(name, *args, &block)
15
+ target.send(name, *args, &block)
16
+ end
17
+
18
+ def create(attributes = {})
19
+ build(attributes).save
20
+ end
21
+
22
+ def build(attributes)
23
+ target_klass.new(attributes).tap do |item|
24
+ item.send("#{source}=", parent)
25
+ target << item
26
+ end
27
+ end
28
+
29
+ def tap
30
+ yield self
31
+ self
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ module Consumerable
2
+ class Configuration
3
+ attr_accessor :logger, :endpoint, :api_version, :content_type,
4
+ :accept_header, :basic_auth_username, :basic_auth_password
5
+
6
+ def logger
7
+ @logger ||= Logger.new(STDOUT)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ require 'typhoeus/adapters/faraday'
2
+
3
+ module Consumerable
4
+ class Connection
5
+
6
+ def self.get(path, options = {})
7
+ connection.get(path, options).body
8
+ end
9
+
10
+ def self.post(path, options = {})
11
+ Consumerable.configuration.logger.info(
12
+ "POSTing #{options} to #{path}"
13
+ )
14
+ connection.post(path, options).body
15
+ end
16
+
17
+ def self.patch(path, options = {})
18
+ connection.patch(path, options).success?
19
+ end
20
+
21
+ def self.delete(path, options = {})
22
+ connection.delete(path, options).success?
23
+ end
24
+
25
+ def self.connection
26
+ Faraday::Connection.new(
27
+ url: Consumerable.configuration.endpoint,
28
+ headers: {
29
+ accept: Consumerable.configuration.accept_header,
30
+ content_type: Consumerable.configuration.content_type
31
+ }
32
+ ) do |builder|
33
+ builder.request Consumerable.configuration.content_type
34
+ builder.response Consumerable.configuration.content_type
35
+ builder.response :raise_error
36
+ builder.use Faraday::Response::Mashify
37
+ builder.adapter :typhoeus
38
+ end.tap do |connection|
39
+ if Consumerable.configuration.basic_auth_username
40
+ connection.basic_auth(
41
+ Consumerable.configuration.basic_auth_username,
42
+ Consumerable.configuration.basic_auth_password
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ module Consumerable
2
+ module Creatable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def create(attributes = {})
7
+ new(attributes).tap(&:save)
8
+ end
9
+
10
+ def create!(attributes = {})
11
+ new(attributes).tap(&:save!)
12
+ end
13
+ end
14
+
15
+ def save
16
+ if valid?
17
+ create_record
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ def save!
24
+ if valid?
25
+ create_record
26
+ else
27
+ raise Consumerable::Errors::RecordInvalid.new(self)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def create_record
34
+ self.attributes =
35
+ Consumerable::Connection.post(
36
+ _inject_path_params(create_path), attributes_for_api.except(:id)
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ module Consumerable
2
+ module Deletable
3
+ extend ActiveSupport::Concern
4
+
5
+ def destroy
6
+ self.id = nil if Consumerable::Connection.
7
+ delete(_inject_path_params(delete_path))
8
+ self
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Consumerable
2
+ module Errors
3
+ extend ActiveSupport::Autoload
4
+ extend ActiveSupport::Concern
5
+
6
+ autoload :RecordInvalid
7
+
8
+ included do
9
+ include Consumerable::Errors::RecordInvalid
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Consumerable
2
+ module Errors
3
+ class RecordInvalid < StandardError
4
+ attr_reader :object
5
+
6
+ def initialize(object)
7
+ @object = object
8
+ super(object)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Consumerable
2
+ module Listable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def all
7
+ init_collection Consumerable::Connection.get(list_path)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ module Consumerable
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Validations
5
+ include Consumerable::Associations
6
+ include Consumerable::Creatable
7
+ include Consumerable::Deletable
8
+ include Consumerable::Listable
9
+ include Virtus
10
+
11
+ module ClassMethods
12
+ def consumerable(options = {})
13
+ define_method :create_path do
14
+ options[:path] || "/#{self.class.to_s.demodulize.downcase.pluralize}"
15
+ end
16
+
17
+ define_method :delete_path do
18
+ "/#{self.class.to_s.demodulize.downcase.pluralize}/:id"
19
+ end
20
+
21
+ define_method :attributes_for_api do
22
+ self.attributes
23
+ end
24
+
25
+ define_method :_inject_path_params do |path|
26
+ path.scan(/:[^\/]*id/).each do |attribute|
27
+ path.gsub!(/#{attribute}/, self.send(attribute.gsub(/^:/, '')))
28
+ end
29
+ path
30
+ end
31
+
32
+ define_singleton_method :list_path do
33
+ options[:path] || "/#{to_s.demodulize.downcase.pluralize}"
34
+ end
35
+
36
+ define_singleton_method :init_collection do |collection|
37
+ collection.map { |item| new(item) }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Consumerable
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: consumerable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Beedle
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: typhoeus
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: virtus
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Quickly write API object mappers without having to re-invent the wheel
126
+ email:
127
+ - mattbeedle@googlemail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - Gemfile
134
+ - LICENSE.txt
135
+ - README.md
136
+ - Rakefile
137
+ - consumerable.gemspec
138
+ - lib/consumerable.rb
139
+ - lib/consumerable/associations.rb
140
+ - lib/consumerable/associations/belongs_to.rb
141
+ - lib/consumerable/associations/has_many.rb
142
+ - lib/consumerable/associations/has_many_proxy.rb
143
+ - lib/consumerable/configuration.rb
144
+ - lib/consumerable/connection.rb
145
+ - lib/consumerable/creatable.rb
146
+ - lib/consumerable/deletable.rb
147
+ - lib/consumerable/errors.rb
148
+ - lib/consumerable/errors/record_invalid.rb
149
+ - lib/consumerable/listable.rb
150
+ - lib/consumerable/model.rb
151
+ - lib/consumerable/version.rb
152
+ homepage: http://www.github.com/mattbeedle/consumerable
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.0.3
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Quickly write API object mappers without having to re-invent the wheel
176
+ test_files: []
177
+ has_rdoc: