sequencescape-client-api 0.3.7 → 0.4.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +44 -0
- data/.rubocop_todo.yml +338 -0
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/README.markdown +13 -0
- data/lib/sequencescape-api.rb +1 -1
- data/lib/sequencescape-api/actions.rb +9 -8
- data/lib/sequencescape-api/associations.rb +8 -6
- data/lib/sequencescape-api/associations/base.rb +1 -1
- data/lib/sequencescape-api/associations/base/instance_methods.rb +10 -8
- data/lib/sequencescape-api/associations/belongs_to.rb +4 -4
- data/lib/sequencescape-api/associations/has_many.rb +14 -13
- data/lib/sequencescape-api/associations/has_many/json.rb +1 -1
- data/lib/sequencescape-api/associations/has_many/validation.rb +1 -1
- data/lib/sequencescape-api/composition.rb +11 -7
- data/lib/sequencescape-api/connection_factory.rb +6 -5
- data/lib/sequencescape-api/connection_factory/actions.rb +17 -12
- data/lib/sequencescape-api/core.rb +12 -7
- data/lib/sequencescape-api/core_ext/array.rb +1 -1
- data/lib/sequencescape-api/core_ext/hash.rb +3 -3
- data/lib/sequencescape-api/errors.rb +2 -2
- data/lib/sequencescape-api/finder_methods.rb +16 -11
- data/lib/sequencescape-api/rails.rb +8 -7
- data/lib/sequencescape-api/resource/active_model.rb +1 -1
- data/lib/sequencescape-api/resource/attribute_groups.rb +11 -7
- data/lib/sequencescape-api/resource/attributes.rb +8 -8
- data/lib/sequencescape-api/resource/instance_methods.rb +17 -9
- data/lib/sequencescape-api/resource/json.rb +18 -15
- data/lib/sequencescape-api/resource/modifications.rb +20 -11
- data/lib/sequencescape-api/resource_model_proxy.rb +7 -5
- data/lib/sequencescape-api/version.rb +1 -1
- data/lib/sequencescape.rb +4 -2
- data/lib/sequencescape/bait_library.rb +1 -1
- data/lib/sequencescape/bait_library_layout.rb +2 -2
- data/lib/sequencescape/barcoded_asset.rb +1 -1
- data/lib/sequencescape/batch.rb +6 -6
- data/lib/sequencescape/behaviour/qced.rb +3 -4
- data/lib/sequencescape/behaviour/receptacle.rb +3 -3
- data/lib/sequencescape/behaviour/state_driven.rb +4 -4
- data/lib/sequencescape/bulk_transfer.rb +1 -1
- data/lib/sequencescape/comment.rb +0 -2
- data/lib/sequencescape/extraction_attribute.rb +6 -0
- data/lib/sequencescape/library_event.rb +1 -1
- data/lib/sequencescape/library_tube.rb +1 -1
- data/lib/sequencescape/lot.rb +0 -1
- data/lib/sequencescape/lot_type.rb +1 -2
- data/lib/sequencescape/order_template.rb +1 -2
- data/lib/sequencescape/pipeline.rb +1 -1
- data/lib/sequencescape/plate.rb +29 -11
- data/lib/sequencescape/plate/pooling.rb +9 -3
- data/lib/sequencescape/plate/well_structure.rb +6 -6
- data/lib/sequencescape/plate_conversion.rb +3 -3
- data/lib/sequencescape/plate_creation.rb +3 -3
- data/lib/sequencescape/plate_purpose.rb +3 -3
- data/lib/sequencescape/plate_template.rb +0 -2
- data/lib/sequencescape/pooled_plate_creation.rb +3 -3
- data/lib/sequencescape/project.rb +2 -1
- data/lib/sequencescape/qc_decision.rb +0 -1
- data/lib/sequencescape/qc_file.rb +0 -3
- data/lib/sequencescape/qcable.rb +0 -3
- data/lib/sequencescape/qcable_creator.rb +1 -4
- data/lib/sequencescape/request.rb +3 -3
- data/lib/sequencescape/search.rb +10 -8
- data/lib/sequencescape/specific_tube_creation.rb +2 -2
- data/lib/sequencescape/stamp.rb +0 -1
- data/lib/sequencescape/state_change.rb +2 -2
- data/lib/sequencescape/study.rb +1 -1
- data/lib/sequencescape/submission.rb +1 -2
- data/lib/sequencescape/tag2_layout.rb +3 -3
- data/lib/sequencescape/tag2_layout_template.rb +2 -2
- data/lib/sequencescape/tag_group.rb +0 -1
- data/lib/sequencescape/tag_layout.rb +3 -3
- data/lib/sequencescape/tag_layout_template.rb +4 -4
- data/lib/sequencescape/transfer.rb +2 -2
- data/lib/sequencescape/transfer_request.rb +5 -7
- data/lib/sequencescape/transfer_template.rb +2 -2
- data/lib/sequencescape/tube.rb +3 -3
- data/lib/sequencescape/tube_creation.rb +3 -3
- data/lib/sequencescape/tube_from_tube_creation.rb +3 -3
- data/lib/sequencescape/tube_purpose.rb +3 -3
- data/lib/sequencescape/user.rb +2 -2
- data/lib/sequencescape/volume_update.rb +1 -1
- data/lib/sequencescape/well.rb +0 -2
- data/lib/sequencescape/work_completion.rb +1 -1
- data/sequencescape-api.gemspec +18 -17
- data/spec/sequencescape-api/associations_spec.rb +4 -2
- data/spec/sequencescape-api/finding_methods_spec.rb +3 -1
- data/spec/sequencescape-api/modifications_spec.rb +17 -16
- data/spec/sequencescape-api/root_spec.rb +11 -6
- data/spec/spec_helper.rb +3 -1
- data/spec/support/contract_helper.rb +18 -10
- data/spec/support/namespaces.rb +9 -9
- data/spec/support/shared_examples.rb +2 -0
- metadata +54 -36
- data/.rvmrc +0 -52
@@ -9,12 +9,12 @@ module Sequencescape::Api::Associations
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def association_methods(association, type, proxy)
|
13
|
-
proxy_class_name = [
|
12
|
+
def association_methods(association, type, proxy) # rubocop:todo Metrics/MethodLength
|
13
|
+
proxy_class_name = [association, type, 'proxy'].join('_').classify
|
14
14
|
const_set(proxy_class_name.to_sym, proxy)
|
15
15
|
|
16
16
|
line = __LINE__ + 1
|
17
|
-
class_eval(%
|
17
|
+
class_eval(%{
|
18
18
|
def #{association}(reload = false)
|
19
19
|
associations[#{association.inspect}] = nil if !!reload
|
20
20
|
associations[#{association.inspect}] ||= #{proxy_class_name}.new(self)
|
@@ -40,7 +40,8 @@ module Sequencescape::Api::Associations
|
|
40
40
|
|
41
41
|
module InstanceMethods
|
42
42
|
def initialize(*args, &block)
|
43
|
-
@associations
|
43
|
+
@associations = {}
|
44
|
+
@errors = nil
|
44
45
|
super
|
45
46
|
end
|
46
47
|
|
@@ -61,12 +62,13 @@ module Sequencescape::Api::Associations
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def attributes_from_path(path, default_value_if_missing = nil)
|
64
|
-
path.to_s.split('.').inject(attributes) { |k,v| k.try(:[], v) } || default_value_if_missing
|
65
|
+
path.to_s.split('.').inject(attributes) { |k, v| k.try(:[], v) } || default_value_if_missing
|
65
66
|
end
|
66
67
|
private :attributes_from_path
|
67
68
|
|
68
69
|
def run_validations!
|
69
|
-
our_result
|
70
|
+
our_result = super
|
71
|
+
their_result = @associations.values.all?(&:run_validations!)
|
70
72
|
our_result and their_result
|
71
73
|
end
|
72
74
|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Sequencescape::Api::Associations::Base::InstanceMethods
|
2
|
-
def self.included(base)
|
2
|
+
def self.included(base) # rubocop:todo Metrics/MethodLength
|
3
3
|
base.class_eval do
|
4
4
|
class_attribute :association, :options
|
5
|
-
class_attribute :default_attributes_if_missing, :
|
5
|
+
class_attribute :default_attributes_if_missing, instance_writer: false
|
6
6
|
|
7
7
|
attr_reader :model
|
8
|
-
|
8
|
+
|
9
|
+
delegate :read_timeout, to: :@owner
|
9
10
|
private :model
|
10
11
|
|
11
12
|
def api(*args, &block)
|
@@ -17,9 +18,9 @@ module Sequencescape::Api::Associations::Base::InstanceMethods
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def initialize(owner, json = nil)
|
20
|
-
@owner
|
21
|
-
@
|
22
|
-
@model
|
21
|
+
@owner = owner
|
22
|
+
@_attributes_ = json.nil? ? owner.attributes_for(association, default_attributes_if_missing) : attributes_from(json)
|
23
|
+
@model = api.model(options[:class_name] || association)
|
23
24
|
end
|
24
25
|
|
25
26
|
def new(*args, &block)
|
@@ -38,8 +39,9 @@ module Sequencescape::Api::Associations::Base::InstanceMethods
|
|
38
39
|
when json.is_a?(String) then { uuid: json, uuid_only: true }
|
39
40
|
when json.is_a?(Hash) then json
|
40
41
|
when json.respond_to?(:map) then json.map(&method(:attributes_from))
|
41
|
-
when json.is_a?(Sequencescape::Api::Resource) then json.as_json(:
|
42
|
-
|
42
|
+
when json.is_a?(Sequencescape::Api::Resource) then json.as_json(force: true, action: :update,
|
43
|
+
root: false, uuid: true)
|
44
|
+
when json.is_a?(Sequencescape::Api::Associations::Base) then json.as_json(force: true, action: :update)
|
43
45
|
else raise json.inspect
|
44
46
|
end
|
45
47
|
end
|
@@ -11,7 +11,7 @@ module Sequencescape::Api::Associations::BelongsTo
|
|
11
11
|
|
12
12
|
def initialize(*args, &block)
|
13
13
|
super
|
14
|
-
@object = new(@
|
14
|
+
@object = new(@_attributes_, false)
|
15
15
|
end
|
16
16
|
|
17
17
|
def update_from_json(json)
|
@@ -50,7 +50,7 @@ module Sequencescape::Api::Associations::BelongsTo
|
|
50
50
|
@owner = owner
|
51
51
|
end
|
52
52
|
|
53
|
-
delegate :loaded, :
|
53
|
+
delegate :loaded, to: :@owner
|
54
54
|
private :loaded
|
55
55
|
|
56
56
|
def new(*args, &block)
|
@@ -71,7 +71,7 @@ module Sequencescape::Api::Associations::BelongsTo
|
|
71
71
|
private :object
|
72
72
|
|
73
73
|
def as_json(options = nil)
|
74
|
-
@object.as_json({ :
|
74
|
+
@object.as_json({ root: false, uuid: false }.reverse_merge(options || {}))
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -109,7 +109,7 @@ module Sequencescape::Api::Associations::BelongsTo
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def belongs_to(association, options = {}, &block)
|
112
|
+
def belongs_to(association, options = {}, &block) # rubocop:todo Metrics/MethodLength
|
113
113
|
association = association.to_sym
|
114
114
|
|
115
115
|
proxy = Class.new(
|
@@ -15,12 +15,14 @@ module Sequencescape::Api::Associations::HasMany
|
|
15
15
|
include Enumerable
|
16
16
|
|
17
17
|
def size
|
18
|
-
return @
|
18
|
+
return @_attributes_['size'] if api.capabilities.size_in_pages?
|
19
|
+
|
19
20
|
all.size
|
20
21
|
end
|
21
22
|
|
22
23
|
def empty?
|
23
|
-
return @
|
24
|
+
return @_attributes_['size'].zero? if api.capabilities.size_in_pages?
|
25
|
+
|
24
26
|
all.empty?
|
25
27
|
end
|
26
28
|
|
@@ -30,16 +32,15 @@ module Sequencescape::Api::Associations::HasMany
|
|
30
32
|
|
31
33
|
def initialize(owner, json = nil)
|
32
34
|
super
|
33
|
-
@cached_all = case
|
34
|
-
|
35
|
-
|
36
|
-
end
|
35
|
+
@cached_all = case json
|
36
|
+
when Array then json.map { |js| new_from(js) }
|
37
|
+
end
|
37
38
|
end
|
38
39
|
|
39
40
|
def new_from(json)
|
40
|
-
case
|
41
|
-
when
|
42
|
-
when
|
41
|
+
case json
|
42
|
+
when String then new(uuid: json) # We've recieved an array of strings, prob. uuids
|
43
|
+
when Hash then new(json)
|
43
44
|
else json
|
44
45
|
end
|
45
46
|
end
|
@@ -60,8 +61,8 @@ module Sequencescape::Api::Associations::HasMany
|
|
60
61
|
super
|
61
62
|
@objects =
|
62
63
|
case
|
63
|
-
when @
|
64
|
-
when @
|
64
|
+
when @_attributes_.is_a?(Array) then @_attributes_.map(&method(:new))
|
65
|
+
when @_attributes_.is_a?(Hash) then @_attributes_.map { |uuid, json| new(json.merge('uuid' => uuid)) }
|
65
66
|
else raise StandardError, "Cannot handle has_many JSON: #{json.inspect}"
|
66
67
|
end
|
67
68
|
end
|
@@ -90,7 +91,7 @@ module Sequencescape::Api::Associations::HasMany
|
|
90
91
|
@objects
|
91
92
|
end
|
92
93
|
|
93
|
-
def each_page
|
94
|
+
def each_page
|
94
95
|
yield(@objects)
|
95
96
|
end
|
96
97
|
|
@@ -105,7 +106,7 @@ module Sequencescape::Api::Associations::HasMany
|
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
108
|
-
def has_many(association, options = {}, &block)
|
109
|
+
def has_many(association, options = {}, &block) # rubocop:todo Metrics/MethodLength
|
109
110
|
association = association.to_sym
|
110
111
|
|
111
112
|
proxy = Class.new(
|
@@ -4,7 +4,7 @@ module Sequencescape::Api::Associations::HasMany::Json
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def as_json(options = nil)
|
7
|
-
options = { :
|
7
|
+
options = { root: false, uuid: true }.reverse_merge(options || {})
|
8
8
|
all.map { |o| o.as_json(options) }.compact
|
9
9
|
end
|
10
10
|
end
|
@@ -11,26 +11,30 @@ module Sequencescape::Api::Composition
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def initialize(owner, attributes)
|
14
|
-
@owner
|
15
|
-
|
14
|
+
@owner = owner
|
15
|
+
@_attributes_ = attributes
|
16
|
+
attributes.each { |k, v| send(:"#{k}=", v) }
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
@_attributes_
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
|
-
def composed_of(name, options = {})
|
26
|
+
def composed_of(name, options = {}) # rubocop:todo Metrics/MethodLength
|
23
27
|
composed_class_name = options[:class_name] || name
|
24
28
|
|
25
29
|
line = __LINE__ + 1
|
26
|
-
class_eval(%
|
30
|
+
class_eval(%{
|
27
31
|
def #{name}
|
28
32
|
return nil unless attributes_for?(#{name.to_s.inspect})
|
29
33
|
api.model(#{composed_class_name.inspect}).new(self, attributes_for(#{name.to_s.inspect}))
|
30
34
|
end
|
31
35
|
|
32
36
|
def #{name}=(attributes)
|
33
|
-
@
|
37
|
+
@_attributes_[#{name.to_s.inspect}] = attributes
|
34
38
|
end
|
35
39
|
}, __FILE__, line)
|
36
40
|
end
|
@@ -8,14 +8,14 @@ class Sequencescape::Api::ConnectionFactory
|
|
8
8
|
def self.create(options)
|
9
9
|
required_options = []
|
10
10
|
required_options.push(:cookie) if options[:authorisation].blank?
|
11
|
-
required_options.push(:url) if
|
11
|
+
required_options.push(:url) if default_url.blank?
|
12
12
|
|
13
|
-
required_options.push(:
|
13
|
+
required_options.push(allow_blank: false)
|
14
14
|
options.required!(*required_options) do |missing|
|
15
15
|
raise ::Sequencescape::Api::Error, "No #{missing.or_sentence} set"
|
16
16
|
end
|
17
17
|
|
18
|
-
options[:url] ||=
|
18
|
+
options[:url] ||= default_url
|
19
19
|
new(options)
|
20
20
|
end
|
21
21
|
|
@@ -23,7 +23,9 @@ class Sequencescape::Api::ConnectionFactory
|
|
23
23
|
private :url, :cookie, :read_timeout
|
24
24
|
|
25
25
|
def initialize(options)
|
26
|
-
@url
|
26
|
+
@url = options[:url]
|
27
|
+
@cookie = options[:cookie]
|
28
|
+
@authorisation = options[:authorisation]
|
27
29
|
@read_timeout = options[:read_timeout] || 120
|
28
30
|
end
|
29
31
|
private_class_method :initialize
|
@@ -37,4 +39,3 @@ class Sequencescape::Api::ConnectionFactory
|
|
37
39
|
|
38
40
|
include Actions
|
39
41
|
end
|
40
|
-
|
@@ -8,7 +8,7 @@ BEGIN {
|
|
8
8
|
Net::HTTP.module_eval do
|
9
9
|
alias_method '__initialize__', 'initialize'
|
10
10
|
|
11
|
-
def initialize(*args
|
11
|
+
def initialize(*args, &block)
|
12
12
|
__initialize__(*args, &block)
|
13
13
|
ensure
|
14
14
|
@debug_output = $stderr if ENV['HTTP_DEBUG']
|
@@ -47,8 +47,9 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
50
|
+
# rubocop:todo Metrics/MethodLength
|
51
|
+
def create(url, body, handler) # rubocop:todo Metrics/CyclomaticComplexity
|
52
|
+
perform(:post, url, jsonify(body, action: :create)) do |response|
|
52
53
|
case response
|
53
54
|
when Net::HTTPCreated then handler.success(parse_json_from(response))
|
54
55
|
when Net::HTTPSuccess then handler.success(parse_json_from(response)) # TODO: should be error!
|
@@ -60,8 +61,10 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
63
65
|
|
64
|
-
|
66
|
+
# rubocop:todo Metrics/MethodLength
|
67
|
+
def create_from_file(url, file, filename, content_type, handler) # rubocop:todo Metrics/CyclomaticComplexity
|
65
68
|
perform_for_file(:post, url, file, filename, content_type) do |response|
|
66
69
|
case response
|
67
70
|
when Net::HTTPCreated then handler.success(parse_json_from(response))
|
@@ -74,9 +77,10 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
74
77
|
end
|
75
78
|
end
|
76
79
|
end
|
80
|
+
# rubocop:enable Metrics/MethodLength
|
77
81
|
|
78
82
|
def update(url, body, handler)
|
79
|
-
perform(:put, url, jsonify(body, :
|
83
|
+
perform(:put, url, jsonify(body, action: :update)) do |response|
|
80
84
|
case response
|
81
85
|
when Net::HTTPSuccess then handler.success(parse_json_from(response))
|
82
86
|
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
@@ -95,7 +99,7 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
95
99
|
end
|
96
100
|
private :handle_redirect
|
97
101
|
|
98
|
-
def perform(http_verb, url, body = nil, accepts = nil
|
102
|
+
def perform(http_verb, url, body = nil, accepts = nil) # rubocop:todo Metrics/MethodLength
|
99
103
|
begin
|
100
104
|
uri = URI.parse(url)
|
101
105
|
rescue URI::InvalidURIError => e
|
@@ -103,12 +107,12 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
103
107
|
end
|
104
108
|
Net::HTTP.start(uri.host, uri.port) do |connection|
|
105
109
|
connection.read_timeout = read_timeout
|
106
|
-
request_headers =
|
110
|
+
request_headers = headers
|
107
111
|
request_headers.merge!('Accept' => accepts) unless accepts.nil?
|
108
112
|
request = Net::HTTP.const_get(http_verb.to_s.classify).new(uri.request_uri, request_headers)
|
109
113
|
unless body.nil?
|
110
114
|
request.content_type = 'application/json'
|
111
|
-
#request.body = body.to_json
|
115
|
+
# request.body = body.to_json
|
112
116
|
request.body = Yajl::Encoder.encode(body)
|
113
117
|
end
|
114
118
|
yield(connection.request(request))
|
@@ -116,14 +120,14 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
116
120
|
end
|
117
121
|
private :perform
|
118
122
|
|
119
|
-
def perform_for_file(http_verb, url, file, filename, content_type
|
123
|
+
def perform_for_file(http_verb, url, file, filename, content_type)
|
120
124
|
uri = URI.parse(url)
|
121
125
|
Net::HTTP.start(uri.host, uri.port) do |connection|
|
122
126
|
connection.read_timeout = read_timeout
|
123
|
-
file_headers = headers.merge!({'Content-Disposition'=> "form-data; filename=\"#{filename}\""})
|
127
|
+
file_headers = headers.merge!({ 'Content-Disposition' => "form-data; filename=\"#{filename}\"" })
|
124
128
|
request = Net::HTTP.const_get(http_verb.to_s.classify).new(uri.request_uri, file_headers)
|
125
129
|
request.content_type = content_type
|
126
|
-
#request.body = body.to_json
|
130
|
+
# request.body = body.to_json
|
127
131
|
request.body = file.read
|
128
132
|
yield(connection.request(request))
|
129
133
|
end
|
@@ -134,13 +138,14 @@ module Sequencescape::Api::ConnectionFactory::Actions
|
|
134
138
|
case
|
135
139
|
when body.nil? then {}
|
136
140
|
when body.is_a?(Hash) then body
|
137
|
-
else body.as_json(options.merge(:
|
141
|
+
else body.as_json(options.merge(root: true))
|
138
142
|
end
|
139
143
|
end
|
140
144
|
private :jsonify
|
141
145
|
|
142
146
|
def parse_json_from(response)
|
143
147
|
raise ServerError, 'server returned non-JSON content' unless response.content_type == 'application/json'
|
148
|
+
|
144
149
|
Yajl::Parser.parse(StringIO.new(response.body))
|
145
150
|
end
|
146
151
|
private :parse_json_from
|
@@ -10,7 +10,8 @@ class Sequencescape::Api
|
|
10
10
|
extend Sequencescape::Api::ConnectionFactory::Helpers
|
11
11
|
|
12
12
|
def initialize(options = {})
|
13
|
-
@models
|
13
|
+
@models = {}
|
14
|
+
@model_namespace = options.delete(:namespace) || Sequencescape
|
14
15
|
@model_namespace = @model_namespace.constantize if @model_namespace.is_a?(String)
|
15
16
|
@connection = self.class.connection_factory.create(options).tap do |connection|
|
16
17
|
connection.root(self)
|
@@ -18,7 +19,8 @@ class Sequencescape::Api
|
|
18
19
|
end
|
19
20
|
|
20
21
|
attr_reader :capabilities
|
21
|
-
|
22
|
+
|
23
|
+
delegate :read, :create, :create_from_file, :update, :retrieve, to: :@connection
|
22
24
|
|
23
25
|
def read_uuid(uuid, handler)
|
24
26
|
read(@connection.url_for_uuid(uuid), handler)
|
@@ -30,22 +32,25 @@ class Sequencescape::Api
|
|
30
32
|
|
31
33
|
def method_missing(name, *args, &block)
|
32
34
|
return super unless @models.keys.include?(name.to_s)
|
35
|
+
|
33
36
|
ResourceModelProxy.new(self, model(name), @models[name.to_s])
|
34
37
|
end
|
35
38
|
protected :method_missing
|
36
39
|
|
37
|
-
def model(name)
|
40
|
+
def model(name) # rubocop:todo Metrics/MethodLength
|
38
41
|
parts = name.to_s.split('::').map(&:classify)
|
39
42
|
raise StandardError, "#{name.inspect} is rooted and that is not supported" if parts.first.blank?
|
43
|
+
|
40
44
|
parts.inject(@model_namespace) { |context, part| context.const_get(part) }
|
41
|
-
rescue NameError =>
|
45
|
+
rescue NameError => e
|
42
46
|
raise if @model_namespace == ::Sequencescape
|
43
|
-
|
47
|
+
|
48
|
+
parts.inject([::Sequencescape, @model_namespace]) do |(source, dest), part|
|
44
49
|
const_from_source = source.const_get(part)
|
45
50
|
if dest.const_defined?(part)
|
46
|
-
[
|
51
|
+
[const_from_source, dest.const_get(part)]
|
47
52
|
else
|
48
|
-
[
|
53
|
+
[const_from_source, dest.const_set(part, const_from_source)]
|
49
54
|
end
|
50
55
|
end.last
|
51
56
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
class Hash
|
2
2
|
# Yields all of the missing keys if there are any so that you can do what you like, like
|
3
3
|
# error maybe?
|
4
|
-
def required!(*keys
|
4
|
+
def required!(*keys)
|
5
5
|
options = keys.extract_options!
|
6
6
|
return if keys.empty?
|
7
7
|
|
8
|
-
allowance_method =
|
8
|
+
allowance_method = options[:allow_blank] == false ? :blank? : :nil?
|
9
9
|
|
10
10
|
missing = keys.inject([]) do |missing, next_key|
|
11
|
-
missing.tap do
|
11
|
+
missing.tap do
|
12
12
|
value = self[next_key]
|
13
13
|
missing << next_key if value.send(allowance_method)
|
14
14
|
end
|