ncore 1.0.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: 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