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.
@@ -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
@@ -0,0 +1,19 @@
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
18
+ *.tmproj
19
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ncore.gemspec
4
+ gemspec
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.
@@ -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
@@ -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,8 @@
1
+ module MyApi
2
+ class Customer < Resource
3
+ crud :all, :find, :create, :update, :delete
4
+
5
+ # has_many :users
6
+
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'ncore/rails/log_subscriber'
2
+
3
+ module AuthRocket
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ include NCore::LogSubscriber
6
+ self.runtime_variable = 'myapi_runtime'
7
+ end
8
+ end
9
+
10
+ MyApi::LogSubscriber.attach_to :my_api
@@ -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
@@ -0,0 +1,3 @@
1
+ module MyApi
2
+ VERSION = "0.0.0"
3
+ end
@@ -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
@@ -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