proxima 0.1.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 +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/proxima/api.rb +50 -0
- data/lib/proxima/attributes.rb +75 -0
- data/lib/proxima/model.rb +165 -0
- data/lib/proxima/paths.rb +54 -0
- data/lib/proxima/serialization.rb +117 -0
- data/lib/proxima/validation.rb +29 -0
- data/lib/proxima/version.rb +5 -0
- data/lib/proxima/watch.rb +11 -0
- data/lib/proxima/watch_array.rb +174 -0
- data/lib/proxima/watch_hash.rb +78 -0
- data/lib/proxima.rb +7 -0
- data/proxima.gemspec +28 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 34bb68254ff2fc2f9ffd3f9c169fd3f66308310c
|
4
|
+
data.tar.gz: f4c5998226f4bf0a072a9019da212e0d68da2fc4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d5b5035f3de7e39e6e06b82f63bebd3c0e43971db2a0d734a579c39fc892a6bf8e9f4e35a1d39f91c38317b4f037e396102b2f2d8c7c2237f1690a79f8e884f
|
7
|
+
data.tar.gz: 34e426d98076d9ab26fb5bce50dde5fb290333f5b4cb81696caafbcadb84c7f92562e2ce1ce5f243b3c32b7d97c49d80726fa73819c3c269b8788c3e5866aeb0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Robert Hurst
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Proxima
|
2
|
+
|
3
|
+
Proxima is a REST Model for use with REST APIs. Proxima's goal is to be
|
4
|
+
flexible, so it refrains from imposing opinions about the remote api. The goal
|
5
|
+
is to allow a Rails application to model any remote REST resource regardless of
|
6
|
+
how the remote REST is implemented. Things like object shape and property names
|
7
|
+
can be completely remapped. Even subpaths can be putted out into attributes.
|
8
|
+
|
9
|
+
Proxima implements the entire Active Model interface.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'proxima'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install proxima
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
To use proxima first create an initializer
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "proxima"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/proxima/api.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
|
4
|
+
module Proxima
|
5
|
+
|
6
|
+
class Api
|
7
|
+
|
8
|
+
attr_accessor :url, :headers
|
9
|
+
|
10
|
+
def initialize(base_url, opts = {})
|
11
|
+
@base_url = base_url
|
12
|
+
@headers = opts[:headers] || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(path, opts = {}, &block)
|
16
|
+
self.request(:post, path, opts, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(path, opts = {}, &block)
|
20
|
+
self.request(:get, path, opts, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(path, opts = {}, &block)
|
24
|
+
self.request(:put, path, opts, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(path, opts = {}, &block)
|
28
|
+
self.request(:delete, path, opts, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def request(method, path, opts = {}, &block)
|
32
|
+
headers = @headers.clone
|
33
|
+
headers.merge!(opts[:headers]) if opts[:headers]
|
34
|
+
headers[:params] = opts[:query] if opts[:query]
|
35
|
+
headers[:content_type] = :json if opts[:json]
|
36
|
+
payload = opts[:json].to_json if opts[:json]
|
37
|
+
|
38
|
+
begin
|
39
|
+
RestClient::Request.execute({
|
40
|
+
method: method,
|
41
|
+
url: @base_url + path,
|
42
|
+
headers: headers,
|
43
|
+
payload: payload
|
44
|
+
}, &block)
|
45
|
+
rescue RestClient::Exception => e
|
46
|
+
e.response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
module Attributes
|
5
|
+
|
6
|
+
def attributes
|
7
|
+
attributes_hash = {}
|
8
|
+
self.class.attributes.each do |attribute, params|
|
9
|
+
value = self.send attribute
|
10
|
+
attributes_hash[attribute.to_s] = value
|
11
|
+
end
|
12
|
+
attributes_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes=(params = {})
|
16
|
+
self.class.attributes.each do |attribute, attribute_params|
|
17
|
+
value = params[attribute]
|
18
|
+
|
19
|
+
if value == nil && default = attribute_params[:default]
|
20
|
+
value = default.respond_to?(:call) ? default.call(self, params) : default
|
21
|
+
end
|
22
|
+
|
23
|
+
if attribute_params[:klass] && !value.is_a?(attribute_params[:klass])
|
24
|
+
klass = attribute_params[:klass]
|
25
|
+
value = klass.new value
|
26
|
+
end
|
27
|
+
|
28
|
+
self.send "#{attribute}=", value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
|
34
|
+
def attributes
|
35
|
+
@attributes ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def attribute(attribute, klass = nil, json_path = nil, params = nil)
|
39
|
+
params ||= json_path if json_path.is_a? Hash
|
40
|
+
params ||= klass if klass.is_a? Hash
|
41
|
+
params ||= {}
|
42
|
+
|
43
|
+
params[:klass] ||= klass if klass.is_a? Class
|
44
|
+
params[:json_path] ||= json_path if json_path.is_a? String
|
45
|
+
params[:json_path] ||= klass if klass.is_a? String
|
46
|
+
params[:json_path] ||= attribute.to_s
|
47
|
+
|
48
|
+
# Create attribute accessors
|
49
|
+
attr_reader attribute
|
50
|
+
define_method("#{attribute}=") do |value|
|
51
|
+
self.persisted = false
|
52
|
+
attribute_will_change! attribute
|
53
|
+
self.instance_variable_set "@#{attribute}", value
|
54
|
+
Proxima.watch value do
|
55
|
+
attribute_will_change! attribute
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create attribute? methods
|
60
|
+
define_method "#{attribute}?" do
|
61
|
+
self.instance_variable_get("@#{attribute}") != nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create suffixed/prefixed attribute methods
|
65
|
+
self.define_attribute_method attribute
|
66
|
+
|
67
|
+
self.attributes[attribute] = params
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.included base
|
72
|
+
base.extend ClassMethods
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'proxima/watch'
|
3
|
+
require 'proxima/attributes'
|
4
|
+
require 'proxima/paths'
|
5
|
+
require 'proxima/serialization'
|
6
|
+
require 'proxima/validation'
|
7
|
+
|
8
|
+
|
9
|
+
module Proxima
|
10
|
+
|
11
|
+
class Model
|
12
|
+
extend ActiveModel::Naming
|
13
|
+
extend ActiveModel::Translation
|
14
|
+
include ActiveModel::AttributeMethods
|
15
|
+
include ActiveModel::Conversion
|
16
|
+
include ActiveModel::Dirty
|
17
|
+
include ActiveModel::Model
|
18
|
+
include ActiveModel::Serializers::JSON
|
19
|
+
include ActiveModel::Validations
|
20
|
+
|
21
|
+
include Proxima::Attributes
|
22
|
+
include Proxima::Paths
|
23
|
+
include Proxima::Serialization
|
24
|
+
include Proxima::Validation
|
25
|
+
|
26
|
+
# TODO: Implement callbacks
|
27
|
+
# extend ActiveModel::Callbacks
|
28
|
+
# define_model_callbacks :create, :update
|
29
|
+
|
30
|
+
def self.api(api = nil)
|
31
|
+
@api = api if api
|
32
|
+
@api
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create(record)
|
36
|
+
return record.map { |record| self.new record } if record.is_a? Array
|
37
|
+
model = self.new(record)
|
38
|
+
return nil unless model.save
|
39
|
+
model
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.find(query = {}, opts = {})
|
43
|
+
opts[:query] = self.convert_query_or_delta_to_json query
|
44
|
+
response = self.api.get self.find_path.call(query), opts
|
45
|
+
|
46
|
+
return [] unless response.code == 200
|
47
|
+
|
48
|
+
records = ActiveSupport::JSON.decode response.body
|
49
|
+
records.map do |record|
|
50
|
+
model = self.new
|
51
|
+
model.from_json record
|
52
|
+
model.new_record = false
|
53
|
+
model
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find_one(query = {}, opts = {})
|
58
|
+
query['$limit'] = 1;
|
59
|
+
self.find(query, opts)[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.count(query = {}, opts = {})
|
63
|
+
query['$limit'] = 0
|
64
|
+
opts[:query] = self.convert_query_or_delta_to_json query
|
65
|
+
response = self.api.get self.find_path.call(query), opts
|
66
|
+
|
67
|
+
return nil unless response.code == 200
|
68
|
+
|
69
|
+
response.headers[:x_total_count] || 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.find_by_id(id, query = {}, opts = {})
|
73
|
+
if opts.empty? && query
|
74
|
+
opts = query
|
75
|
+
query = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
query[:id] = id
|
79
|
+
response = self.api.get self.find_by_id_path.call(query), opts
|
80
|
+
|
81
|
+
return nil unless response.code == 200
|
82
|
+
|
83
|
+
model = self.new
|
84
|
+
model.from_json response.body
|
85
|
+
model.new_record = false
|
86
|
+
model
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(record = {})
|
90
|
+
self.new_record = true
|
91
|
+
self.attributes = record
|
92
|
+
end
|
93
|
+
|
94
|
+
def persisted?
|
95
|
+
@persisted
|
96
|
+
end
|
97
|
+
|
98
|
+
def persisted=(val)
|
99
|
+
@persisted = !!val
|
100
|
+
changes_applied if val
|
101
|
+
end
|
102
|
+
|
103
|
+
def new_record?
|
104
|
+
@new_record
|
105
|
+
end
|
106
|
+
|
107
|
+
def new_record=(val)
|
108
|
+
@new_record = !!val
|
109
|
+
@persisted = !val
|
110
|
+
clear_changes_information unless val
|
111
|
+
end
|
112
|
+
|
113
|
+
def save(options = {})
|
114
|
+
return false unless self.valid?
|
115
|
+
|
116
|
+
if self.new_record?
|
117
|
+
path = self.class.create_path.call self.to_h
|
118
|
+
payload = { json: self.as_json(options) }
|
119
|
+
response = self.class.api.post path, payload
|
120
|
+
|
121
|
+
return false unless response.code == 201
|
122
|
+
|
123
|
+
self.from_json response.body, options[:include_root]
|
124
|
+
self.new_record = false
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
|
128
|
+
return true if self.persisted?
|
129
|
+
|
130
|
+
options[:flatten] = true if options[:flatten] == nil
|
131
|
+
path = self.class.update_by_id_path.call self.to_h
|
132
|
+
payload = { json: self.as_json(options) }
|
133
|
+
response = self.class.api.put path, payload
|
134
|
+
|
135
|
+
return false unless response.code == 204
|
136
|
+
self.persisted = true
|
137
|
+
end
|
138
|
+
|
139
|
+
def reload!
|
140
|
+
self.clear_changes_information
|
141
|
+
end
|
142
|
+
|
143
|
+
def rollback!
|
144
|
+
self.restore_attributes
|
145
|
+
end
|
146
|
+
|
147
|
+
def destroy
|
148
|
+
return false if new_record?
|
149
|
+
|
150
|
+
response = self.class.api.delete(self.class.delete_by_id_path.call(self.to_h))
|
151
|
+
|
152
|
+
return false unless response.code == 204
|
153
|
+
self.persisted = true
|
154
|
+
end
|
155
|
+
|
156
|
+
def restore
|
157
|
+
return false if new_record?
|
158
|
+
|
159
|
+
response = self.class.api.post(self.class.restore_by_id_path.call(self.to_h))
|
160
|
+
|
161
|
+
return false unless response.code == 204
|
162
|
+
self.persisted = true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
module Paths
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def base_uri(base_uri = nil)
|
8
|
+
base_uri = ->(m) { base_uri } if base_uri.is_a? String
|
9
|
+
@base_uri = base_uri if base_uri
|
10
|
+
@base_uri ||= ->(m) { "" }
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_path(create_path = nil)
|
14
|
+
create_path = ->(m) { create_path } if create_path.is_a? String
|
15
|
+
@create_path = create_path if create_path
|
16
|
+
@create_path ||= ->(m) { "#{@base_uri.call(m)}" }
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_path(find_path = nil)
|
20
|
+
find_path = ->(m) { find_path } if find_path.is_a? String
|
21
|
+
@find_path = find_path if find_path
|
22
|
+
@find_path ||= ->(m) { "#{@base_uri.call(m)}" }
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_by_id_path(find_by_id_path = nil)
|
26
|
+
find_by_id_path = ->(m) { find_by_id_path } if find_by_id_path.is_a? String
|
27
|
+
@find_by_id_path = find_by_id_path if find_by_id_path
|
28
|
+
@find_by_id_path ||= ->(m) { "#{@base_uri.call(m)}/#{m[:id]}" }
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_by_id_path(update_by_id_path = nil)
|
32
|
+
update_by_id_path = ->(m) { update_by_id_path } if update_by_id_path.is_a? String
|
33
|
+
@update_by_id_path = update_by_id_path if update_by_id_path
|
34
|
+
@update_by_id_path ||= ->(m) { "#{@base_uri.call(m)}/#{m[:id]}" }
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_by_id_path(delete_by_id_path = nil)
|
38
|
+
delete_by_id_path = ->(m) { delete_by_id_path } if delete_by_id_path.is_a? String
|
39
|
+
@delete_by_id_path = delete_by_id_path if delete_by_id_path
|
40
|
+
@delete_by_id_path ||= ->(m) { "#{@base_uri.call(m)}/#{m[:id]}" }
|
41
|
+
end
|
42
|
+
|
43
|
+
def restore_by_id_path(restore_by_id_path = nil)
|
44
|
+
restore_by_id_path = ->(m) { restore_by_id_path } if restore_by_id_path.is_a? String
|
45
|
+
@restore_by_id_path = restore_by_id_path if restore_by_id_path
|
46
|
+
@restore_by_id_path ||= ->(m) { "#{@base_uri.call(m)}/restore/#{m[:id]}" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.included base
|
51
|
+
base.extend ClassMethods
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
module Serialization
|
5
|
+
|
6
|
+
def from_json(json, include_root = self.include_root_in_json)
|
7
|
+
json = ActiveSupport::JSON.decode(json) if json.is_a?(String)
|
8
|
+
json = json.values.first if include_root
|
9
|
+
hash = {}
|
10
|
+
|
11
|
+
self.class.attributes.each do |attribute, params|
|
12
|
+
json_path = params[:json_path]
|
13
|
+
json_path_chunks = json_path.split '.'
|
14
|
+
json_ctx = json
|
15
|
+
for json_path_chunk in json_path_chunks
|
16
|
+
json_ctx = json_ctx[json_path_chunk]
|
17
|
+
break if json_ctx.nil?
|
18
|
+
end
|
19
|
+
value = json_ctx
|
20
|
+
|
21
|
+
next unless value
|
22
|
+
|
23
|
+
if params[:klass] && params[:klass].respond_to?(:from_json)
|
24
|
+
value = params[:klass].from_json(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
hash[attribute] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
self.attributes = hash
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def as_json(options = {})
|
35
|
+
hash = serializable_hash options
|
36
|
+
json = {}
|
37
|
+
self.class.attributes.each do |attribute, params|
|
38
|
+
next if (
|
39
|
+
!options.key?(:include_clean) && !send("#{attribute}_changed?") ||
|
40
|
+
!options.key?(:include_nil) && hash[attribute.to_s] == nil
|
41
|
+
)
|
42
|
+
|
43
|
+
json_path = params[:json_path]
|
44
|
+
value = hash[attribute.to_s]
|
45
|
+
value = value.as_json if value.respond_to? :as_json
|
46
|
+
|
47
|
+
if options.key? :flatten
|
48
|
+
json[json_path] = value
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
json_path_chunks = json_path.split '.'
|
53
|
+
last_json_path_chunk = json_path_chunks.pop
|
54
|
+
json_ctx = json
|
55
|
+
for json_path_chunk in json_path_chunks
|
56
|
+
json_ctx[json_path_chunk] = {} unless json_ctx[json_path_chunk].is_a?(Hash)
|
57
|
+
json_ctx = json_ctx[json_path_chunk]
|
58
|
+
end
|
59
|
+
json_ctx[last_json_path_chunk] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
root = if options.key? :root
|
63
|
+
options[:root]
|
64
|
+
else
|
65
|
+
self.include_root_in_json
|
66
|
+
end
|
67
|
+
|
68
|
+
if root
|
69
|
+
root = self.class.model_name.element if root == true
|
70
|
+
{ root => json }
|
71
|
+
else
|
72
|
+
json
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_h
|
77
|
+
hash = {}
|
78
|
+
self.class.attributes.each do |attribute, params|
|
79
|
+
hash[attribute] = self.send attribute
|
80
|
+
end
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
|
84
|
+
module ClassMethods
|
85
|
+
|
86
|
+
def from_json(json, include_root=self.include_root_in_json)
|
87
|
+
json = ActiveSupport::JSON.decode(json) if json.is_a? String
|
88
|
+
json = json.values.first if include_root
|
89
|
+
|
90
|
+
if json.is_a? Array
|
91
|
+
return json.map { |json| self.new.from_json json }
|
92
|
+
end
|
93
|
+
|
94
|
+
self.new.from_json json
|
95
|
+
end
|
96
|
+
|
97
|
+
def convert_query_or_delta_to_json(query)
|
98
|
+
json_query = {}
|
99
|
+
query.each do |attribute, val|
|
100
|
+
attr_str = attribute.to_s
|
101
|
+
json_path = attributes[attribute] ? attributes[attribute][:json_path] : attr_str
|
102
|
+
|
103
|
+
json_query[json_path] = unless attr_str[0] == '$' && val.is_a?(Hash)
|
104
|
+
val
|
105
|
+
else
|
106
|
+
self.convert_query_or_delta_to_json val
|
107
|
+
end
|
108
|
+
end
|
109
|
+
json_query
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.included base
|
114
|
+
base.extend ClassMethods
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
module Validation
|
5
|
+
|
6
|
+
def read_attribute_for_validation(attr)
|
7
|
+
self.send attr
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.errors
|
11
|
+
@errors ||= ActiveModel::Errors.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def human_attribute_name(attr, options = {})
|
17
|
+
attr
|
18
|
+
end
|
19
|
+
|
20
|
+
def lookup_ancestors
|
21
|
+
[self]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included base
|
26
|
+
base.extend ClassMethods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
|
5
|
+
def self.watch_array(array, &on_change)
|
6
|
+
|
7
|
+
array.instance_variable_set(:@on_change, on_change)
|
8
|
+
|
9
|
+
array.each do |value|
|
10
|
+
Proxima.watch(value, &on_change)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << array
|
14
|
+
|
15
|
+
def <<(*args)
|
16
|
+
result = super
|
17
|
+
args.each do |value|
|
18
|
+
Proxima.watch(value, &@on_change)
|
19
|
+
end
|
20
|
+
@on_change.call
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(*args)
|
25
|
+
result = super
|
26
|
+
Proxima.watch(args[2] || args[1], &@on_change)
|
27
|
+
@on_change.call
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear(*args)
|
32
|
+
result = super
|
33
|
+
@on_change.call
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
def collect!(*args)
|
38
|
+
result = super
|
39
|
+
@on_change.call
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def compact!(*args)
|
44
|
+
result = super
|
45
|
+
@on_change.call if result
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(*args)
|
50
|
+
result = super
|
51
|
+
@on_change.call if result
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete_at(*args)
|
56
|
+
result = super
|
57
|
+
@on_change.call if result
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_if(*args)
|
62
|
+
result = super
|
63
|
+
@on_change.call
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def fill(*args)
|
68
|
+
result = super
|
69
|
+
@on_change.call
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def flatten!(*args)
|
74
|
+
result = super
|
75
|
+
@on_change.call
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def replace(*args)
|
80
|
+
result = super
|
81
|
+
@on_change.call
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
def insert(*args)
|
86
|
+
args[1..-1].each do |value|
|
87
|
+
Proxima.watch(value, &@on_change)
|
88
|
+
end
|
89
|
+
result = super
|
90
|
+
@on_change.call if args[1] != nil
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
def pop(*args)
|
95
|
+
result = super
|
96
|
+
@on_change.call if args[0] == nil || args[0] > 0
|
97
|
+
result
|
98
|
+
end
|
99
|
+
|
100
|
+
def push(*args)
|
101
|
+
args.each do |value|
|
102
|
+
Proxima.watch(value, &@on_change)
|
103
|
+
end
|
104
|
+
result = super
|
105
|
+
@on_change.call if args.length > 0
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def reject!(*args)
|
110
|
+
result = super
|
111
|
+
@on_change.call if result
|
112
|
+
result
|
113
|
+
end
|
114
|
+
|
115
|
+
def reverse!(*args)
|
116
|
+
result = super
|
117
|
+
@on_change.call
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
def rotate!(*args)
|
122
|
+
result = super
|
123
|
+
@on_change.call if args[0] != 0
|
124
|
+
result
|
125
|
+
end
|
126
|
+
|
127
|
+
def select!(*args)
|
128
|
+
result = super
|
129
|
+
@on_change.call if result
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
def shift(*args)
|
134
|
+
result = super
|
135
|
+
@on_change.call if args[0] == nil || args[0] > 0
|
136
|
+
result
|
137
|
+
end
|
138
|
+
|
139
|
+
def shuffle!(*args)
|
140
|
+
result = super
|
141
|
+
@on_change.call
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
def slice!(*args)
|
146
|
+
result = super
|
147
|
+
if result.is_a? Array
|
148
|
+
@on_change.call if result.length > 0
|
149
|
+
else
|
150
|
+
@on_change.call if result != nil
|
151
|
+
end
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
def sort!(*args)
|
156
|
+
result = super
|
157
|
+
@on_change.call
|
158
|
+
result
|
159
|
+
end
|
160
|
+
|
161
|
+
def sort_by!(*args)
|
162
|
+
result = super
|
163
|
+
@on_change.call
|
164
|
+
result
|
165
|
+
end
|
166
|
+
|
167
|
+
def uniq!(*args)
|
168
|
+
result = super
|
169
|
+
@on_change.call if result
|
170
|
+
result
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Proxima
|
4
|
+
|
5
|
+
def self.watch_hash(hash, &on_change)
|
6
|
+
|
7
|
+
hash.instance_variable_set(:@on_change, on_change)
|
8
|
+
|
9
|
+
hash.values.each do |value|
|
10
|
+
Proxima.watch(value, &on_change)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << hash
|
14
|
+
|
15
|
+
def []=(*args)
|
16
|
+
Proxima.watch(args[1], &@on_change)
|
17
|
+
result = super
|
18
|
+
@on_change.call if args[1]
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear(*args)
|
23
|
+
result = super
|
24
|
+
@on_change.call
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(*args)
|
29
|
+
result = super
|
30
|
+
@on_change.call if result
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_if(*args)
|
35
|
+
result = super
|
36
|
+
@on_change.call
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge!(*args)
|
41
|
+
args[0].values.each do |value|
|
42
|
+
Proxima.watch(value, &@on_change)
|
43
|
+
end
|
44
|
+
result = super
|
45
|
+
@on_change.call
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def reject!(*args)
|
50
|
+
result = super
|
51
|
+
@on_change.call if result
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def select!(*args)
|
56
|
+
result = super
|
57
|
+
@on_change.call if result
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def store(*args)
|
62
|
+
Proxima.watch(args[1], &@on_change)
|
63
|
+
result = super
|
64
|
+
@on_change.call
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
def update(*args)
|
69
|
+
args[0].values.each do |value|
|
70
|
+
Proxima.watch(value, &@on_change)
|
71
|
+
end
|
72
|
+
result = super
|
73
|
+
@on_change.call
|
74
|
+
result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/proxima.rb
ADDED
data/proxima.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'proxima/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "proxima"
|
8
|
+
spec.version = Proxima::VERSION
|
9
|
+
spec.authors = ["Robert Hurst"]
|
10
|
+
spec.email = ["robertwhurst@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "REST Models for Ruby on Rails"
|
13
|
+
spec.description = "Proxima is a gem the provides models for use with REST endpoints. These models are based upon the Active Model interface"
|
14
|
+
spec.homepage = "https://github.com/fintechdev/proxima"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.5"
|
25
|
+
|
26
|
+
spec.add_dependency "activemodel", '~> 5.0', '>= 5.0.0'
|
27
|
+
spec.add_dependency "rest-client", '~> 2.0', '>= 2.0.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: proxima
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Hurst
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activemodel
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 5.0.0
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '5.0'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 5.0.0
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rest-client
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.0'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 2.0.0
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '2.0'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.0.0
|
95
|
+
description: Proxima is a gem the provides models for use with REST endpoints. These
|
96
|
+
models are based upon the Active Model interface
|
97
|
+
email:
|
98
|
+
- robertwhurst@gmail.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- ".gitignore"
|
104
|
+
- ".rspec"
|
105
|
+
- ".travis.yml"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- bin/console
|
111
|
+
- bin/setup
|
112
|
+
- lib/proxima.rb
|
113
|
+
- lib/proxima/api.rb
|
114
|
+
- lib/proxima/attributes.rb
|
115
|
+
- lib/proxima/model.rb
|
116
|
+
- lib/proxima/paths.rb
|
117
|
+
- lib/proxima/serialization.rb
|
118
|
+
- lib/proxima/validation.rb
|
119
|
+
- lib/proxima/version.rb
|
120
|
+
- lib/proxima/watch.rb
|
121
|
+
- lib/proxima/watch_array.rb
|
122
|
+
- lib/proxima/watch_hash.rb
|
123
|
+
- proxima.gemspec
|
124
|
+
homepage: https://github.com/fintechdev/proxima
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.4.5.1
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: REST Models for Ruby on Rails
|
148
|
+
test_files: []
|