ncore 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/example/lib/my_api.rb +12 -0
- data/example/lib/my_api/api_config.rb +33 -0
- data/example/lib/my_api/customer.rb +8 -0
- data/example/lib/my_api/rails/log_subscriber.rb +10 -0
- data/example/lib/my_api/rails/railtie.rb +13 -0
- data/example/lib/my_api/version.rb +3 -0
- data/lib/ncore.rb +14 -0
- data/lib/ncore/associations.rb +81 -0
- data/lib/ncore/attributes.rb +157 -0
- data/lib/ncore/base.rb +35 -0
- data/lib/ncore/builder.rb +34 -0
- data/lib/ncore/client.rb +301 -0
- data/lib/ncore/collection.rb +11 -0
- data/lib/ncore/configuration.rb +56 -0
- data/lib/ncore/exceptions.rb +45 -0
- data/lib/ncore/identity.rb +26 -0
- data/lib/ncore/lifecycle.rb +36 -0
- data/lib/ncore/methods/all.rb +30 -0
- data/lib/ncore/methods/build.rb +16 -0
- data/lib/ncore/methods/count.rb +13 -0
- data/lib/ncore/methods/create.rb +33 -0
- data/lib/ncore/methods/delete.rb +19 -0
- data/lib/ncore/methods/delete_single.rb +19 -0
- data/lib/ncore/methods/find.rb +26 -0
- data/lib/ncore/methods/find_single.rb +34 -0
- data/lib/ncore/methods/update.rb +29 -0
- data/lib/ncore/rails/active_model.rb +44 -0
- data/lib/ncore/rails/log_subscriber.rb +116 -0
- data/lib/ncore/singleton_base.rb +33 -0
- data/lib/ncore/ssl/ca-certificates.crt +4027 -0
- data/lib/ncore/util.rb +61 -0
- data/lib/ncore/version.rb +3 -0
- data/ncore.gemspec +27 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 687bf7af7ae063bc540bc41c18ea36eada77d008
|
4
|
+
data.tar.gz: 9221a21313353c2921edcb6ce78835836fad2a66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77cc977814f4adfa60276f2a9b3404e8804a37658c687ccaf1cf0e4c1cf4819bfaa72d8aeb1d691e58c37f922c1d6362c50865a4dd877da367d383ec6d7b2a08
|
7
|
+
data.tar.gz: 861663d398ace5c44bd3f8cf49257f7cef95e8127acf999895dc8dc300f37adc3b8fd45dcb8ff28e8b55d42d07e3ddb981343d9098b46195ff2238d523bf5844
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Notioneer, Inc.
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# NCore
|
2
|
+
|
3
|
+
NCore is a Ruby gem designed to help build REST API clients. It is not an API
|
4
|
+
client by itself, but provides several useful building blocks to build one.
|
5
|
+
|
6
|
+
It relies on `rest-client` for HTTP handling, `multi_json` for JSON parsing
|
7
|
+
(and supports only JSON), and `activesupport`.
|
8
|
+
|
9
|
+
See `example/` for the beginning of an actual api client.
|
10
|
+
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'ncore'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install ncore
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
TODO: Write usage instructions here
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
1. Fork it
|
33
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
35
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'ncore'
|
2
|
+
|
3
|
+
%w(version api_config).each do |f|
|
4
|
+
require "my_api/#{f}"
|
5
|
+
end
|
6
|
+
|
7
|
+
%w(customer).each do |f|
|
8
|
+
require "my_api/#{f}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# optional; adds "MyApi: 16.3ms" to the Rails request log footer
|
12
|
+
require 'my_api/rails/railtie' if defined?(Rails)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MyApi
|
2
|
+
include NCore::Builder
|
3
|
+
|
4
|
+
configure do
|
5
|
+
self.default_url = 'https://api.example.com/v1/'
|
6
|
+
|
7
|
+
self.default_headers = {
|
8
|
+
accept: 'application/json',
|
9
|
+
content_type: 'application/json',
|
10
|
+
user_agent: "MyApi/ruby v#{VERSION}"
|
11
|
+
}
|
12
|
+
|
13
|
+
if ENV['API_USER'] && ENV['API_KEY']
|
14
|
+
self.credentials = {
|
15
|
+
api_user: ENV['API_USER'],
|
16
|
+
api_key: ENV['API_KEY']
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
self.debug = false
|
21
|
+
|
22
|
+
self.strict_attributes = false
|
23
|
+
|
24
|
+
self.instrument_key = 'request.my_api'
|
25
|
+
|
26
|
+
self.status_page = 'http://my.api.status.page'
|
27
|
+
|
28
|
+
self.auth_header_prefix = 'X-MyApi'
|
29
|
+
|
30
|
+
self.credentials_error_message = %Q{Missing API credentials. Set default credentials using "MyApi.credentials = {api_user: YOUR_API_USER, api_key: YOUR_API_KEY}"}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MyApi
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
initializer "my_api.log_runtime" do |app|
|
5
|
+
require 'my_api/rails/log_subscriber'
|
6
|
+
ActiveSupport.on_load(:action_controller) do
|
7
|
+
include NCore::ControllerRuntime
|
8
|
+
register_api_runtime MyApi::LogSubscriber, 'MyApi'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/ncore.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'excon'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
%w(version builder configuration associations attributes client collection exceptions identity lifecycle util base singleton_base).each do |f|
|
7
|
+
require "ncore/#{f}"
|
8
|
+
end
|
9
|
+
|
10
|
+
%w(all build count create delete delete_single find find_single update).each do |f|
|
11
|
+
require "ncore/methods/#{f}"
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'ncore/rails/active_model' if defined?(::ActiveModel)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module NCore
|
2
|
+
module Associations
|
3
|
+
|
4
|
+
def has_many(assoc, klass=nil)
|
5
|
+
assoc = assoc.to_s
|
6
|
+
klass ||= "#{module_name}::#{assoc.camelize.singularize}"
|
7
|
+
key = "#{attrib_name}_id"
|
8
|
+
class_eval <<-M1, __FILE__, __LINE__+1
|
9
|
+
def #{assoc}(params={})
|
10
|
+
return [] unless id
|
11
|
+
reload = params.delete :reload
|
12
|
+
if params.empty?
|
13
|
+
# only cache unfiltered, default api call
|
14
|
+
@attribs[:#{assoc}] = (!reload && @attribs[:#{assoc}]) || #{klass}.all({#{key}: id}, api_creds)
|
15
|
+
else
|
16
|
+
#{klass}.all(params.merge(#{key}: id), api_creds)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
M1
|
20
|
+
class_eval <<-M2, __FILE__, __LINE__+1
|
21
|
+
def find_#{assoc.singularize}(aid, params={})
|
22
|
+
raise UnsavedObjectError unless id
|
23
|
+
## #{assoc}.detect{|o| o.id == aid} || raise(RecordNotFound)
|
24
|
+
# @attribs[:#{assoc}].detect{|o| o.id == aid} || #{klass}.find(aid, {#{key}: id}, api_creds)
|
25
|
+
#{klass}.find(aid, {#{key}: id}.reverse_merge(params), api_creds)
|
26
|
+
end
|
27
|
+
M2
|
28
|
+
# will always return the object; check .errors? or .valid? to see how it went
|
29
|
+
class_eval <<-M3, __FILE__, __LINE__+1
|
30
|
+
def create_#{assoc.singularize}(params={})
|
31
|
+
raise UnsavedObjectError unless id
|
32
|
+
#{klass}.create(params.merge(#{key}: id), api_creds)
|
33
|
+
end
|
34
|
+
M3
|
35
|
+
# will always return the object; check .errors? or .valid? to see how it went
|
36
|
+
class_eval <<-M4, __FILE__, __LINE__+1
|
37
|
+
def update_#{assoc.singularize}(aid, params={})
|
38
|
+
obj = find_#{assoc.singularize}(aid)
|
39
|
+
obj.update(params)
|
40
|
+
obj
|
41
|
+
end
|
42
|
+
M4
|
43
|
+
class_eval <<-M5, __FILE__, __LINE__+1
|
44
|
+
def create_#{assoc.singularize}!(params={})
|
45
|
+
raise UnsavedObjectError unless id
|
46
|
+
#{klass}.create!(params.merge(#{key}: id), api_creds)
|
47
|
+
end
|
48
|
+
M5
|
49
|
+
class_eval <<-M6, __FILE__, __LINE__+1
|
50
|
+
def update_#{assoc.singularize}!(aid, params={})
|
51
|
+
obj = find_#{assoc.singularize}(aid)
|
52
|
+
obj.save!(params)
|
53
|
+
end
|
54
|
+
M6
|
55
|
+
end
|
56
|
+
|
57
|
+
def belongs_to(assoc, klass=nil)
|
58
|
+
assoc = assoc.to_s
|
59
|
+
klass ||= "#{module_name}::#{assoc.camelize}"
|
60
|
+
class_eval <<-M1, __FILE__, __LINE__+1
|
61
|
+
attr :#{assoc}_id
|
62
|
+
def #{assoc}(params={})
|
63
|
+
return nil unless #{assoc}_id
|
64
|
+
if params.empty?
|
65
|
+
# only cache unfiltered, default api call
|
66
|
+
@attribs[:#{assoc}] ||= #{klass}.find(#{assoc}_id, {}, api_creds)
|
67
|
+
else
|
68
|
+
#{klass}.find(#{assoc}_id, params, api_creds)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
M1
|
72
|
+
class_eval <<-M2, __FILE__, __LINE__+1
|
73
|
+
def #{assoc}_id=(v)
|
74
|
+
@attribs[:#{assoc}] = nil unless @attribs[:#{assoc}_id] == v
|
75
|
+
@attribs[:#{assoc}_id] = v
|
76
|
+
end
|
77
|
+
M2
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module NCore
|
2
|
+
module Attributes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attr :id
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def attr(*attrs)
|
12
|
+
attrs.each do |attr|
|
13
|
+
class_eval <<-AR, __FILE__, __LINE__+1
|
14
|
+
def #{attr}
|
15
|
+
self[:#{attr}]
|
16
|
+
end
|
17
|
+
AR
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def attr_datetime(*attrs)
|
22
|
+
attrs.each do |attr|
|
23
|
+
class_eval <<-AD, __FILE__, __LINE__+1
|
24
|
+
def #{attr}
|
25
|
+
case self[:#{attr}]
|
26
|
+
when String
|
27
|
+
Time.parse(self[:#{attr}]).utc
|
28
|
+
when Numeric
|
29
|
+
Time.at(self[:#{attr}]).utc
|
30
|
+
else
|
31
|
+
self[:#{attr}]
|
32
|
+
end
|
33
|
+
rescue ArgumentError, TypeError
|
34
|
+
self[:#{attr}]
|
35
|
+
end
|
36
|
+
AD
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def attr_decimal(*attrs)
|
41
|
+
attrs.each do |attr|
|
42
|
+
class_eval <<-AD, __FILE__, __LINE__+1
|
43
|
+
def #{attr}
|
44
|
+
case self[:#{attr}]
|
45
|
+
when String
|
46
|
+
BigMoney.new(self[:#{attr}])
|
47
|
+
else
|
48
|
+
self[:#{attr}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
AD
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
attr_accessor :api_creds
|
58
|
+
attr_accessor :metadata, :errors
|
59
|
+
|
60
|
+
|
61
|
+
def initialize(attribs={}, api_creds=nil)
|
62
|
+
@attribs = {}.with_indifferent_access
|
63
|
+
@api_creds = api_creds
|
64
|
+
|
65
|
+
attribs = attribs.dup.with_indifferent_access
|
66
|
+
if attribs.keys.sort == %w(data error metadata)
|
67
|
+
load_attrs = attribs
|
68
|
+
else
|
69
|
+
load_attrs = {
|
70
|
+
metadata: attribs.delete(:metadata),
|
71
|
+
errors: attribs.delete(:errors),
|
72
|
+
data: attribs.delete(:data) || attribs
|
73
|
+
}
|
74
|
+
end
|
75
|
+
load(load_attrs)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def attributes
|
80
|
+
Util.deep_clone(@attribs)
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def [](attr)
|
85
|
+
@attribs[attr]
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Method names known to cause strange behavior in other libraries
|
90
|
+
# where said libraries check for these methods to determine other
|
91
|
+
# behavior.
|
92
|
+
KNOWN_FALSE_TRIGGERS = %w(map each)
|
93
|
+
|
94
|
+
def respond_to?(method, incl_private=false)
|
95
|
+
m2 = method.to_s.sub(/(\?)$/,'')
|
96
|
+
if method.to_s =~ /\=$/
|
97
|
+
super
|
98
|
+
elsif @attribs.has_key?(m2)
|
99
|
+
true
|
100
|
+
elsif !self.class.strict_attributes && !KNOWN_FALSE_TRIGGERS.include?(m2)
|
101
|
+
true
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def method_missing(method, *args, &block)
|
111
|
+
case method.to_s
|
112
|
+
when /(.+)\?$/
|
113
|
+
if @attribs.has_key?($1) || respond_to?($1.to_sym)
|
114
|
+
!! self[$1]
|
115
|
+
else
|
116
|
+
super
|
117
|
+
end
|
118
|
+
when /\=$/
|
119
|
+
super
|
120
|
+
else
|
121
|
+
if @attribs.has_key?(method) || !self.class.strict_attributes
|
122
|
+
self[method]
|
123
|
+
else
|
124
|
+
super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def load(parsed)
|
131
|
+
self.metadata = parsed[:metadata] || {}.with_indifferent_access
|
132
|
+
self.errors = parsed[:errors] || {}.with_indifferent_access
|
133
|
+
parsed[:data].each do |k,v|
|
134
|
+
if respond_to? "#{k}="
|
135
|
+
send "#{k}=", self.class.interpret_type(v, api_creds)
|
136
|
+
else
|
137
|
+
@attribs[k] = self.class.interpret_type(v, api_creds)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
class BigMoney < BigDecimal
|
147
|
+
|
148
|
+
def to_s
|
149
|
+
if (self % BigDecimal.new('0.01')) == 0
|
150
|
+
'%.2f' % self
|
151
|
+
else
|
152
|
+
super
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
data/lib/ncore/base.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module NCore
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend Associations
|
7
|
+
include Attributes
|
8
|
+
include Client
|
9
|
+
include Identity
|
10
|
+
include Lifecycle
|
11
|
+
include Util
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def crud(*types)
|
16
|
+
include All if types.include? :all
|
17
|
+
include Build if types.include? :build
|
18
|
+
include Count if types.include? :count
|
19
|
+
include Create if types.include? :create
|
20
|
+
include Delete if types.include? :delete
|
21
|
+
include Find if types.include? :find
|
22
|
+
include Update if types.include? :update
|
23
|
+
end
|
24
|
+
|
25
|
+
def url
|
26
|
+
class_name.underscore.pluralize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def url
|
31
|
+
"#{self.class.url}/#{CGI.escape id||'-'}"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|