netbox-client-ruby 0.0.1
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 +13 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -0
- data/Dockerfile +12 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +138 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.test.yml +5 -0
- data/docker-compose.yml +37 -0
- data/docker/start.sh +3 -0
- data/docker/start.test.sh +3 -0
- data/lib/netbox-client-ruby.rb +1 -0
- data/lib/netbox_client_ruby.rb +25 -0
- data/lib/netbox_client_ruby/api.rb +18 -0
- data/lib/netbox_client_ruby/api/dcim.rb +55 -0
- data/lib/netbox_client_ruby/api/dcim/device.rb +31 -0
- data/lib/netbox_client_ruby/api/dcim/device_role.rb +12 -0
- data/lib/netbox_client_ruby/api/dcim/device_roles.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/device_type.rb +26 -0
- data/lib/netbox_client_ruby/api/dcim/device_types.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/devices.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/interface.rb +14 -0
- data/lib/netbox_client_ruby/api/dcim/interfaces.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/manufacturer.rb +12 -0
- data/lib/netbox_client_ruby/api/dcim/manufacturers.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/platform.rb +12 -0
- data/lib/netbox_client_ruby/api/dcim/platforms.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/rack.rb +12 -0
- data/lib/netbox_client_ruby/api/dcim/racks.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/region.rb +13 -0
- data/lib/netbox_client_ruby/api/dcim/regions.rb +19 -0
- data/lib/netbox_client_ruby/api/dcim/site.rb +15 -0
- data/lib/netbox_client_ruby/api/dcim/sites.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam.rb +53 -0
- data/lib/netbox_client_ruby/api/ipam/aggregate.rb +14 -0
- data/lib/netbox_client_ruby/api/ipam/aggregates.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/ip_address.rb +42 -0
- data/lib/netbox_client_ruby/api/ipam/ip_addresses.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/prefix.rb +47 -0
- data/lib/netbox_client_ruby/api/ipam/prefixes.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/rir.rb +12 -0
- data/lib/netbox_client_ruby/api/ipam/rirs.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/role.rb +12 -0
- data/lib/netbox_client_ruby/api/ipam/roles.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/vlan.rb +40 -0
- data/lib/netbox_client_ruby/api/ipam/vlan_group.rb +14 -0
- data/lib/netbox_client_ruby/api/ipam/vlan_groups.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/vlans.rb +19 -0
- data/lib/netbox_client_ruby/api/ipam/vrf.rb +14 -0
- data/lib/netbox_client_ruby/api/ipam/vrfs.rb +19 -0
- data/lib/netbox_client_ruby/api/tenancy.rb +25 -0
- data/lib/netbox_client_ruby/api/tenancy/tenant.rb +14 -0
- data/lib/netbox_client_ruby/api/tenancy/tenant_group.rb +12 -0
- data/lib/netbox_client_ruby/api/tenancy/tenant_groups.rb +19 -0
- data/lib/netbox_client_ruby/api/tenancy/tenants.rb +19 -0
- data/lib/netbox_client_ruby/communication.rb +74 -0
- data/lib/netbox_client_ruby/connection.rb +33 -0
- data/lib/netbox_client_ruby/entities.rb +200 -0
- data/lib/netbox_client_ruby/entity.rb +345 -0
- data/lib/netbox_client_ruby/error/client_error.rb +4 -0
- data/lib/netbox_client_ruby/error/local_error.rb +4 -0
- data/lib/netbox_client_ruby/error/remote_error.rb +4 -0
- data/netbox-client-ruby.gemspec +43 -0
- data/netbox.env +17 -0
- metadata +255 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'faraday_middleware'
|
|
3
|
+
require 'faraday/detailed_logger'
|
|
4
|
+
require 'netbox_client_ruby/error/local_error'
|
|
5
|
+
|
|
6
|
+
module NetboxClientRuby
|
|
7
|
+
class Connection
|
|
8
|
+
def self.new
|
|
9
|
+
build_faraday
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.headers
|
|
13
|
+
headers = {}
|
|
14
|
+
netbox_auth_config = NetboxClientRuby.config.netbox.auth
|
|
15
|
+
|
|
16
|
+
raise LocalError, 'The authorization_token has not been configured.' unless netbox_auth_config.token
|
|
17
|
+
|
|
18
|
+
headers['Authorization'] = "Token #{netbox_auth_config.token}"
|
|
19
|
+
headers
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private_class_method def self.build_faraday
|
|
23
|
+
config = NetboxClientRuby.config
|
|
24
|
+
Faraday.new(url: config.netbox.api_base_url, headers: headers) do |faraday|
|
|
25
|
+
faraday.request :json
|
|
26
|
+
faraday.response config.faraday.logger if config.faraday.logger
|
|
27
|
+
faraday.response :json, content_type: /\bjson$/
|
|
28
|
+
faraday.adapter config.faraday.adapter || Faraday.default_adapter
|
|
29
|
+
faraday.options.merge NetboxClientRuby.config.faraday.request_options
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
require 'netbox_client_ruby/entity'
|
|
2
|
+
require 'netbox_client_ruby/error/local_error'
|
|
3
|
+
require 'pry'
|
|
4
|
+
|
|
5
|
+
module NetboxClientRuby
|
|
6
|
+
module Entities
|
|
7
|
+
include NetboxClientRuby::Communication
|
|
8
|
+
|
|
9
|
+
def self.included(other_klass)
|
|
10
|
+
other_klass.extend ClassMethods
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def self.extended(other_klass)
|
|
15
|
+
@other_klass = other_klass
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def data_key(key = 'data')
|
|
19
|
+
@data_key ||= key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def count_key(key = 'count')
|
|
23
|
+
@count_key ||= key
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def limit(limit = nil)
|
|
27
|
+
return @limit if @limit
|
|
28
|
+
|
|
29
|
+
@limit = limit || NetboxClientRuby.config.netbox.pagination.default_limit
|
|
30
|
+
|
|
31
|
+
if @limit.nil?
|
|
32
|
+
raise ArgumentError,
|
|
33
|
+
"Whether 'limit' nor 'default_limit' are defined."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
check_limit @limit
|
|
37
|
+
|
|
38
|
+
@limit
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_limit(limit)
|
|
42
|
+
max_limit = NetboxClientRuby.config.netbox.pagination.max_limit
|
|
43
|
+
if limit.nil?
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"'limit' can not be nil."
|
|
46
|
+
elsif !limit.is_a?(Numeric)
|
|
47
|
+
raise ArgumentError,
|
|
48
|
+
"The limit '#{limit}' is not numeric but it has to be."
|
|
49
|
+
elsif !limit.integer?
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"The limit '#{limit}' is not integer but it has to be."
|
|
52
|
+
elsif limit.negative?
|
|
53
|
+
raise ArgumentError,
|
|
54
|
+
"The limit '#{limit}' is below zero, but it should be zero or bigger."
|
|
55
|
+
elsif limit > max_limit
|
|
56
|
+
raise ArgumentError,
|
|
57
|
+
"The limit '#{limit}' is bigger than the configured limit value ('#{max_limit}')."
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def entity_creator(creator = nil)
|
|
62
|
+
return @entity_creator if @entity_creator
|
|
63
|
+
|
|
64
|
+
raise ArgumentError, '"entity_creator" has not been defined.' unless creator
|
|
65
|
+
|
|
66
|
+
@entity_creator = creator
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def path(path = nil)
|
|
70
|
+
return @path if @path
|
|
71
|
+
|
|
72
|
+
raise ArgumentError, '"path" has not been defined.' unless path
|
|
73
|
+
|
|
74
|
+
@path = path
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def filter(filter)
|
|
79
|
+
raise ArgumentError, '"filter" expects a hash' unless filter.is_a? Hash
|
|
80
|
+
|
|
81
|
+
@filter = filter
|
|
82
|
+
reset
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def all
|
|
87
|
+
@instance_limit = NetboxClientRuby.config.netbox.pagination.max_limit
|
|
88
|
+
reset
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def limit(limit)
|
|
93
|
+
self.class.check_limit limit unless limit.nil?
|
|
94
|
+
|
|
95
|
+
@instance_limit = limit
|
|
96
|
+
reset
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def offset(offset)
|
|
101
|
+
raise ArgumentError, "The offset '#{offset}' is not numeric." unless offset.is_a? Numeric
|
|
102
|
+
raise ArgumentError, "The offset '#{offset}' must not be negative." if offset.negative?
|
|
103
|
+
|
|
104
|
+
@offset = offset
|
|
105
|
+
reset
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def page(page)
|
|
110
|
+
raise ArgumentError, "The offset '#{page}' is not numeric but has to be." unless page.is_a? Numeric
|
|
111
|
+
raise ArgumentError, "The offset '#{page}' must be integer but isn't." unless page.integer?
|
|
112
|
+
raise ArgumentError, "The offset '#{page}' must not be negative but is." if page.negative?
|
|
113
|
+
|
|
114
|
+
limit = @instance_limit || self.class.limit
|
|
115
|
+
offset(limit * page)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def [](index)
|
|
119
|
+
return nil if length <= index
|
|
120
|
+
|
|
121
|
+
as_entity raw_data_array[index]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def as_array
|
|
125
|
+
raw_data_array.map { |raw_entity| as_entity raw_entity }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# The number of entities that have been fetched
|
|
130
|
+
def length
|
|
131
|
+
raw_data_array.length
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# The total number of available entities for that query
|
|
136
|
+
def total
|
|
137
|
+
data[self.class.count_key]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def reload
|
|
141
|
+
@data = get
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def raw_data!
|
|
146
|
+
data
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
alias get! reload
|
|
150
|
+
alias size length
|
|
151
|
+
alias count total
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def reset
|
|
156
|
+
@data = nil
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def raw_data_array
|
|
160
|
+
data[self.class.data_key] || []
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def data
|
|
164
|
+
@data ||= get
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def get
|
|
168
|
+
response connection.get path_with_parameters
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def path_with_parameters
|
|
172
|
+
self.class.path + path_parameters
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def path_parameters
|
|
176
|
+
params = []
|
|
177
|
+
|
|
178
|
+
params << @filter
|
|
179
|
+
params << { limit: @instance_limit || self.class.limit }
|
|
180
|
+
params << { offset: @offset } if @offset
|
|
181
|
+
|
|
182
|
+
join_path_parameters(params)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def join_path_parameters(params)
|
|
186
|
+
return '' if params.empty?
|
|
187
|
+
|
|
188
|
+
'?' + params.compact.map do |param_obj|
|
|
189
|
+
URI.encode_www_form param_obj
|
|
190
|
+
end.join('&')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def as_entity(raw_entity)
|
|
194
|
+
entity_creator_method = method self.class.entity_creator
|
|
195
|
+
entity = entity_creator_method.call raw_entity
|
|
196
|
+
entity.data = raw_entity
|
|
197
|
+
entity
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
require 'netbox_client_ruby/communication'
|
|
2
|
+
require 'netbox_client_ruby/error/local_error'
|
|
3
|
+
|
|
4
|
+
module NetboxClientRuby
|
|
5
|
+
module Entity
|
|
6
|
+
include NetboxClientRuby::Communication
|
|
7
|
+
|
|
8
|
+
def self.included(other_klass)
|
|
9
|
+
other_klass.extend ClassMethods
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
##
|
|
14
|
+
# Expects ids in the following format:
|
|
15
|
+
# id 'an_id_field'
|
|
16
|
+
# id :an_id_field
|
|
17
|
+
# id 'an_id_field', 'another_id_field'
|
|
18
|
+
# id :an_id_field, :another_id_field
|
|
19
|
+
# id an_id_field: 'id_field_in_data'
|
|
20
|
+
# id an_id_field: :id_field_in_data
|
|
21
|
+
# id 'an_id_field' => :id_field_in_data
|
|
22
|
+
# id 'an_id_field' => 'id_field_in_data'
|
|
23
|
+
# id an_id_field: 'id_field_in_data', :another_id_field: 'id_field2_in_data'
|
|
24
|
+
#
|
|
25
|
+
def id(*fields)
|
|
26
|
+
return @id_fields if @id_fields
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "No 'id' was defined, but one is expected." if fields.empty?
|
|
29
|
+
|
|
30
|
+
@id_fields = {}
|
|
31
|
+
if fields.first.is_a?(Hash)
|
|
32
|
+
fields.first.each { |key, value| @id_fields[key.to_s] = value.to_s }
|
|
33
|
+
else
|
|
34
|
+
fields.map(&:to_s).each do |field|
|
|
35
|
+
field_as_string = field.to_s
|
|
36
|
+
@id_fields[field_as_string] = field_as_string
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@id_fields.keys.each do |field|
|
|
41
|
+
define_method(field) { instance_variable_get "@#{field}" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@id_fields
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def readonly_fields(*fields)
|
|
48
|
+
return @readonly_fields if @readonly_fields
|
|
49
|
+
|
|
50
|
+
@readonly_fields = fields.map(&:to_s)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deletable(deletable = false)
|
|
54
|
+
@deletable ||= deletable
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def path(path = nil)
|
|
58
|
+
@path ||= path
|
|
59
|
+
|
|
60
|
+
return @path if @path
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, "No argument to 'path' was given."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def creation_path(creation_path = nil)
|
|
66
|
+
@creation_path ||= creation_path
|
|
67
|
+
|
|
68
|
+
return @creation_path if @creation_path
|
|
69
|
+
|
|
70
|
+
raise ArgumentError, "No argument to 'creation_path' was given."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# allowed values:
|
|
74
|
+
# <code>
|
|
75
|
+
# object_fields :field_a, :field_b, :field_c, 'fieldname_as_string'
|
|
76
|
+
# # next is ame as previous
|
|
77
|
+
# object_fields field_a: nil, field_b: nil, field_c: nil, 'fieldname_as_string': nil
|
|
78
|
+
# # next defines the types of the objects
|
|
79
|
+
# object_fields field_a: Klass, field_b: proc { |data| Klass.new data }, field_c: nil, 'fieldname_as_string'
|
|
80
|
+
# </code>
|
|
81
|
+
def object_fields(*fields_to_class_map)
|
|
82
|
+
return @object_fields if @object_fields
|
|
83
|
+
|
|
84
|
+
if fields_to_class_map.nil? || fields_to_class_map.empty?
|
|
85
|
+
@object_fields = {}
|
|
86
|
+
else
|
|
87
|
+
fields_map = sanitize_mapping(fields_to_class_map)
|
|
88
|
+
@object_fields = fields_map
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def sanitize_mapping(fields_to_class_map)
|
|
93
|
+
fields_map = {}
|
|
94
|
+
fields_to_class_map.each do |field_definition|
|
|
95
|
+
if field_definition.is_a?(Hash)
|
|
96
|
+
fields_to_class_map[0].each do |field, klass_or_proc|
|
|
97
|
+
fields_map[field.to_s] = klass_or_proc
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
fields_map[field_definition.to_s] = nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
fields_map
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def initialize(given_ids = nil)
|
|
108
|
+
return self if given_ids.nil?
|
|
109
|
+
|
|
110
|
+
if id_fields.count == 1 && !given_ids.is_a?(Hash)
|
|
111
|
+
instance_variable_set("@#{id_fields.keys.first}", given_ids)
|
|
112
|
+
return self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
check_given_ids(given_ids)
|
|
116
|
+
|
|
117
|
+
given_ids.each { |id_field, id_value| instance_variable_set "@#{id_field}", id_value }
|
|
118
|
+
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def revert
|
|
123
|
+
dirty_data.clear
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def reload
|
|
128
|
+
@data = get
|
|
129
|
+
revert
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def save
|
|
134
|
+
return post unless ids_set?
|
|
135
|
+
patch
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def create(raw_data)
|
|
139
|
+
raise LocalError, "Can't 'create', this object already exists" if ids_set?
|
|
140
|
+
|
|
141
|
+
@dirty_data = raw_data
|
|
142
|
+
post
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def delete
|
|
146
|
+
raise NetboxClientRuby::LocalError, "Can't delete unless deletable=true" unless deletable
|
|
147
|
+
return self if @deleted
|
|
148
|
+
|
|
149
|
+
@data = response connection.delete path
|
|
150
|
+
@deleted = true
|
|
151
|
+
revert
|
|
152
|
+
self
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def update(updated_fields)
|
|
156
|
+
checked_updated_fields = {}
|
|
157
|
+
updated_fields.each do |key, values|
|
|
158
|
+
s_key = key.to_s
|
|
159
|
+
|
|
160
|
+
checked_updated_fields[s_key] = values unless readonly_fields.include? s_key
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
dirty_data.merge! checked_updated_fields
|
|
164
|
+
patch
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def raw_data!
|
|
168
|
+
data
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def [](name)
|
|
172
|
+
s_name = name.to_s
|
|
173
|
+
dirty_data[s_name] || data[s_name]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def []=(name, value)
|
|
177
|
+
dirty_data[name.to_s] = value
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def method_missing(name_as_symbol, *args, &block)
|
|
181
|
+
name = name_as_symbol.to_s
|
|
182
|
+
|
|
183
|
+
if name.end_with?('=')
|
|
184
|
+
is_readonly_field = readonly_fields.include?(name[0..-2])
|
|
185
|
+
is_instance_variable = instance_variables.include?("@#{name[0..-2]}".to_sym)
|
|
186
|
+
not_this_classes_business = is_readonly_field || is_instance_variable
|
|
187
|
+
|
|
188
|
+
if not_this_classes_business
|
|
189
|
+
super
|
|
190
|
+
else
|
|
191
|
+
dirty_data[name[0..-2]] = args[0]
|
|
192
|
+
return args[0]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
return objectify(name, object_fields[name]) if object_fields.keys.include? name
|
|
197
|
+
|
|
198
|
+
return dirty_data[name] if dirty_data.keys.include? name
|
|
199
|
+
return data[name] if data.is_a?(Hash) && data.keys.include?(name)
|
|
200
|
+
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def respond_to_missing?(name_as_symbol, *args)
|
|
205
|
+
name = name_as_symbol.to_s
|
|
206
|
+
|
|
207
|
+
return false if name.end_with?('=') && readonly_fields.include?(name[0..-2])
|
|
208
|
+
return false if name.end_with?('=') && instance_variables.include?(name[0..-2])
|
|
209
|
+
|
|
210
|
+
return true if object_fields.keys.include? name
|
|
211
|
+
|
|
212
|
+
return true if dirty_data.keys.include? name
|
|
213
|
+
return true if data.is_a?(Hash) && data.keys.include?(name)
|
|
214
|
+
|
|
215
|
+
super
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# :internal: Used to pre-populate an entity
|
|
220
|
+
def data=(data)
|
|
221
|
+
@data = data
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
alias get! reload
|
|
225
|
+
alias remove delete
|
|
226
|
+
|
|
227
|
+
private
|
|
228
|
+
|
|
229
|
+
def post
|
|
230
|
+
return self if dirty_data.empty?
|
|
231
|
+
|
|
232
|
+
@data = response connection.post creation_path, dirty_data
|
|
233
|
+
extract_ids
|
|
234
|
+
revert
|
|
235
|
+
self
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def patch
|
|
239
|
+
return self if dirty_data.empty?
|
|
240
|
+
|
|
241
|
+
@data ||= {}
|
|
242
|
+
response_data = response connection.patch(path, dirty_data)
|
|
243
|
+
@data.merge! response_data
|
|
244
|
+
revert
|
|
245
|
+
self
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def check_given_ids(given_ids)
|
|
249
|
+
if !given_ids.is_a? Hash
|
|
250
|
+
raise ArgumentError, "'#{self.class}.new' expects the argument to be a Hash when multiple ids are defined"
|
|
251
|
+
elsif given_ids.empty?
|
|
252
|
+
raise ArgumentError, "'#{self.class}.new' expects a the argument to not be empty."
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
missing_ids = id_fields.keys - given_ids.keys.map(&:to_s)
|
|
256
|
+
|
|
257
|
+
unless missing_ids.empty?
|
|
258
|
+
raise ArgumentError,
|
|
259
|
+
"'#{self.class}.new' expects a value for all defined IDs. The following values are missing: '#{missing_ids.join(', ')}'"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def data
|
|
264
|
+
@data ||= get
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def get
|
|
268
|
+
response connection.get path unless @deleted
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def readonly_fields
|
|
272
|
+
self.class.readonly_fields
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def deletable
|
|
276
|
+
self.class.deletable
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def object_fields
|
|
280
|
+
self.class.object_fields
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def data_to_obj(raw_data, klass_or_proc = nil)
|
|
284
|
+
if klass_or_proc.nil?
|
|
285
|
+
hash_to_object raw_data
|
|
286
|
+
elsif klass_or_proc.is_a? Proc
|
|
287
|
+
klass_or_proc.call raw_data
|
|
288
|
+
else
|
|
289
|
+
klass_or_proc.new raw_data
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def objectify(name, klass_or_proc = nil)
|
|
294
|
+
raw_data = data[name]
|
|
295
|
+
|
|
296
|
+
return nil if raw_data.nil?
|
|
297
|
+
|
|
298
|
+
return data_to_obj(raw_data, klass_or_proc) unless raw_data.is_a? Array
|
|
299
|
+
|
|
300
|
+
data[name].map do |raw_data_entry|
|
|
301
|
+
data_to_obj(raw_data_entry, klass_or_proc)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def creation_path
|
|
306
|
+
@creation_path ||= replace_path_variables_in self.class.creation_path
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def path
|
|
310
|
+
@path ||= replace_path_variables_in self.class.path
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def replace_path_variables_in(path)
|
|
314
|
+
interpreted_path = path.clone
|
|
315
|
+
path.scan(/:([a-zA-Z_][a-zA-Z0-9_]+[!?=]?)/) do |match, *|
|
|
316
|
+
path_variable_value = send(match)
|
|
317
|
+
return interpreted_path.gsub! ":#{match}", path_variable_value.to_s unless path_variable_value.nil?
|
|
318
|
+
raise LocalError, "Received 'nil' while replacing ':#{match}' in '#{path}' with a value."
|
|
319
|
+
end
|
|
320
|
+
interpreted_path
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def dirty_data
|
|
324
|
+
@dirty_data ||= {}
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def id_fields
|
|
328
|
+
self.class.id
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def extract_ids
|
|
332
|
+
id_fields.each do |id_attr, id_field|
|
|
333
|
+
unless data.key?(id_field)
|
|
334
|
+
raise LocalError, "Can't find the id field '#{id_field}' in the received data."
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
instance_variable_set("@#{id_attr}", data[id_field])
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def ids_set?
|
|
342
|
+
id_fields.map { |id_attr, _| instance_variable_get("@#{id_attr}") }.all?
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|