sequencescape-client-api 0.3.5
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 +7 -0
- data/.rspec +1 -0
- data/.rvmrc +52 -0
- data/.yardoc/checksums +96 -0
- data/.yardoc/complete +0 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/Gemfile +4 -0
- data/README.markdown +4 -0
- data/Rakefile +2 -0
- data/doc/Array.html +192 -0
- data/doc/BaitLibrary.html +139 -0
- data/doc/Hash.html +242 -0
- data/doc/NilClass.html +214 -0
- data/doc/Sequencescape/Api/Actions/ClassActionHelpers.html +213 -0
- data/doc/Sequencescape/Api/Actions/InstanceActionHelpers.html +282 -0
- data/doc/Sequencescape/Api/Actions.html +197 -0
- data/doc/Sequencescape/Api/Associations/Base/InstanceMethods.html +352 -0
- data/doc/Sequencescape/Api/Associations/Base.html +237 -0
- data/doc/Sequencescape/Api/Associations/BelongsTo/AssociationProxy.html +332 -0
- data/doc/Sequencescape/Api/Associations/BelongsTo/CommonBehaviour/LoadHandler.html +279 -0
- data/doc/Sequencescape/Api/Associations/BelongsTo/CommonBehaviour.html +432 -0
- data/doc/Sequencescape/Api/Associations/BelongsTo/InlineAssociationProxy.html +173 -0
- data/doc/Sequencescape/Api/Associations/BelongsTo.html +212 -0
- data/doc/Sequencescape/Api/Associations/HasMany/AssociationProxy.html +366 -0
- data/doc/Sequencescape/Api/Associations/HasMany/InlineAssociationProxy.html +529 -0
- data/doc/Sequencescape/Api/Associations/HasMany/Json.html +250 -0
- data/doc/Sequencescape/Api/Associations/HasMany/Validation/CompositeErrors.html +493 -0
- data/doc/Sequencescape/Api/Associations/HasMany/Validation.html +245 -0
- data/doc/Sequencescape/Api/Associations/HasMany.html +212 -0
- data/doc/Sequencescape/Api/Associations/InstanceMethods/CompositeErrors.html +379 -0
- data/doc/Sequencescape/Api/Associations/InstanceMethods.html +496 -0
- data/doc/Sequencescape/Api/Associations.html +201 -0
- data/doc/Sequencescape/Api/BasicErrorHandling.html +371 -0
- data/doc/Sequencescape/Api/Composition/Target.html +262 -0
- data/doc/Sequencescape/Api/Composition.html +215 -0
- data/doc/Sequencescape/Api/ConnectionFactory/Actions.html +611 -0
- data/doc/Sequencescape/Api/ConnectionFactory/Helpers.html +233 -0
- data/doc/Sequencescape/Api/ConnectionFactory.html +321 -0
- data/doc/Sequencescape/Api/ErrorHandling/TurnOffValidationOfUuidOnlyRecords.html +189 -0
- data/doc/Sequencescape/Api/ErrorHandling.html +201 -0
- data/doc/Sequencescape/Api/FinderMethods/AllHandler.html +279 -0
- data/doc/Sequencescape/Api/FinderMethods/Caching.html +183 -0
- data/doc/Sequencescape/Api/FinderMethods/Delegation.html +189 -0
- data/doc/Sequencescape/Api/FinderMethods/FindByUuidHandler.html +279 -0
- data/doc/Sequencescape/Api/FinderMethods.html +364 -0
- data/doc/Sequencescape/Api/GeneralError.html +176 -0
- data/doc/Sequencescape/Api/JsonError.html +205 -0
- data/doc/Sequencescape/Api/ModifyingHandler.html +359 -0
- data/doc/Sequencescape/Api/PageOfResults/UpdateHandler.html +279 -0
- data/doc/Sequencescape/Api/PageOfResults.html +611 -0
- data/doc/Sequencescape/Api/Rails/ApplicationController.html +207 -0
- data/doc/Sequencescape/Api/Rails/Resourced.html +269 -0
- data/doc/Sequencescape/Api/Rails.html +117 -0
- data/doc/Sequencescape/Api/Resource/ActiveModel.html +287 -0
- data/doc/Sequencescape/Api/Resource/Attributes/InstanceMethods.html +272 -0
- data/doc/Sequencescape/Api/Resource/Attributes.html +199 -0
- data/doc/Sequencescape/Api/Resource/Groups/InstanceMethods.html +378 -0
- data/doc/Sequencescape/Api/Resource/Groups/Json.html +112 -0
- data/doc/Sequencescape/Api/Resource/Groups/Proxy/InstanceMethods.html +256 -0
- data/doc/Sequencescape/Api/Resource/Groups/Proxy.html +177 -0
- data/doc/Sequencescape/Api/Resource/Groups.html +270 -0
- data/doc/Sequencescape/Api/Resource/InstanceMethods.html +344 -0
- data/doc/Sequencescape/Api/Resource/Json/ClassMethods.html +243 -0
- data/doc/Sequencescape/Api/Resource/Json/CoercionHandler.html +279 -0
- data/doc/Sequencescape/Api/Resource/Json.html +405 -0
- data/doc/Sequencescape/Api/Resource/Modifications.html +295 -0
- data/doc/Sequencescape/Api/Resource.html +436 -0
- data/doc/Sequencescape/Api/ResourceInvalid.html +289 -0
- data/doc/Sequencescape/Api/ResourceModelProxy.html +444 -0
- data/doc/Sequencescape/Api/Version1.html +218 -0
- data/doc/Sequencescape/Api/Version2.html +222 -0
- data/doc/Sequencescape/Api.html +704 -0
- data/doc/Sequencescape/Asset.html +262 -0
- data/doc/Sequencescape/AssetAudit.html +258 -0
- data/doc/Sequencescape/AssetGroup.html +258 -0
- data/doc/Sequencescape/BaitLibraryLayout.html +258 -0
- data/doc/Sequencescape/BarcodePrinter.html +258 -0
- data/doc/Sequencescape/BarcodedAsset.html +293 -0
- data/doc/Sequencescape/Batch.html +327 -0
- data/doc/Sequencescape/Behaviour/Barcoded.html +195 -0
- data/doc/Sequencescape/Behaviour/Labeled.html +189 -0
- data/doc/Sequencescape/Behaviour/Qced/QcFile.html +244 -0
- data/doc/Sequencescape/Behaviour/Qced.html +199 -0
- data/doc/Sequencescape/Behaviour/Receptacle/Aliquot.html +258 -0
- data/doc/Sequencescape/Behaviour/Receptacle.html +195 -0
- data/doc/Sequencescape/Behaviour/StateDriven.html +205 -0
- data/doc/Sequencescape/Behaviour.html +130 -0
- data/doc/Sequencescape/BulkTransfer.html +258 -0
- data/doc/Sequencescape/Comment.html +258 -0
- data/doc/Sequencescape/Lane.html +282 -0
- data/doc/Sequencescape/LibraryEvent.html +258 -0
- data/doc/Sequencescape/LibraryTube.html +322 -0
- data/doc/Sequencescape/Lot.html +258 -0
- data/doc/Sequencescape/LotType/LotCreator.html +182 -0
- data/doc/Sequencescape/LotType.html +268 -0
- data/doc/Sequencescape/MultiplexedLibraryTube.html +326 -0
- data/doc/Sequencescape/Order.html +338 -0
- data/doc/Sequencescape/OrderTemplate/OrderCreator.html +182 -0
- data/doc/Sequencescape/OrderTemplate.html +268 -0
- data/doc/Sequencescape/Pipeline.html +258 -0
- data/doc/Sequencescape/Plate/CommentsCreation.html +184 -0
- data/doc/Sequencescape/Plate/CurrentVolumeSubstraction.html +236 -0
- data/doc/Sequencescape/Plate/Pooling.html +249 -0
- data/doc/Sequencescape/Plate/WellStructure.html +313 -0
- data/doc/Sequencescape/Plate.html +446 -0
- data/doc/Sequencescape/PlateConversion.html +258 -0
- data/doc/Sequencescape/PlateCreation.html +258 -0
- data/doc/Sequencescape/PlatePurpose/PlateCreation.html +186 -0
- data/doc/Sequencescape/PlatePurpose.html +268 -0
- data/doc/Sequencescape/PlateTemplate.html +282 -0
- data/doc/Sequencescape/PooledPlateCreation.html +258 -0
- data/doc/Sequencescape/Project.html +258 -0
- data/doc/Sequencescape/QcDecision.html +258 -0
- data/doc/Sequencescape/QcFile.html +327 -0
- data/doc/Sequencescape/Qcable.html +258 -0
- data/doc/Sequencescape/QcableCreator.html +258 -0
- data/doc/Sequencescape/Request.html +258 -0
- data/doc/Sequencescape/Robot.html +258 -0
- data/doc/Sequencescape/Sample.html +258 -0
- data/doc/Sequencescape/SampleTube.html +318 -0
- data/doc/Sequencescape/Search/BaseHandler.html +234 -0
- data/doc/Sequencescape/Search/MultipleResultHandler.html +276 -0
- data/doc/Sequencescape/Search/SingleResultHandler.html +303 -0
- data/doc/Sequencescape/Search.html +431 -0
- data/doc/Sequencescape/SpecificTubeCreation.html +258 -0
- data/doc/Sequencescape/Stamp.html +258 -0
- data/doc/Sequencescape/StateChange.html +258 -0
- data/doc/Sequencescape/Study.html +258 -0
- data/doc/Sequencescape/Submission.html +258 -0
- data/doc/Sequencescape/SubmissionPool.html +258 -0
- data/doc/Sequencescape/Tag/Group.html +219 -0
- data/doc/Sequencescape/Tag.html +149 -0
- data/doc/Sequencescape/Tag2Layout.html +258 -0
- data/doc/Sequencescape/Tag2LayoutTemplate.html +258 -0
- data/doc/Sequencescape/TagGroup.html +258 -0
- data/doc/Sequencescape/TagLayout.html +258 -0
- data/doc/Sequencescape/TagLayoutTemplate.html +258 -0
- data/doc/Sequencescape/Template.html +258 -0
- data/doc/Sequencescape/Transfer.html +258 -0
- data/doc/Sequencescape/TransferTemplate.html +258 -0
- data/doc/Sequencescape/Tube.html +319 -0
- data/doc/Sequencescape/TubeCreation.html +258 -0
- data/doc/Sequencescape/TubeFromTubeCreation.html +258 -0
- data/doc/Sequencescape/TubePurpose/TubeCreation.html +186 -0
- data/doc/Sequencescape/TubePurpose.html +268 -0
- data/doc/Sequencescape/User.html +258 -0
- data/doc/Sequencescape/VolumeUpdate.html +258 -0
- data/doc/Sequencescape/Well.html +362 -0
- data/doc/Sequencescape.html +121 -0
- data/doc/_index.html +1248 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +481 -0
- data/doc/file.README.html +76 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +76 -0
- data/doc/js/app.js +243 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1411 -0
- data/doc/top-level-namespace.html +114 -0
- data/lib/sequencescape/asset.rb +10 -0
- data/lib/sequencescape/asset_audit.rb +7 -0
- data/lib/sequencescape/asset_group.rb +9 -0
- data/lib/sequencescape/bait_library.rb +14 -0
- data/lib/sequencescape/bait_library_layout.rb +9 -0
- data/lib/sequencescape/barcode_printer.rb +13 -0
- data/lib/sequencescape/barcoded_asset.rb +12 -0
- data/lib/sequencescape/batch.rb +24 -0
- data/lib/sequencescape/behaviour/barcoded.rb +15 -0
- data/lib/sequencescape/behaviour/labeled.rb +14 -0
- data/lib/sequencescape/behaviour/qced.rb +31 -0
- data/lib/sequencescape/behaviour/receptacle.rb +21 -0
- data/lib/sequencescape/behaviour/state_driven.rb +19 -0
- data/lib/sequencescape/bulk_transfer.rb +8 -0
- data/lib/sequencescape/comment.rb +7 -0
- data/lib/sequencescape/custom_metadatum_collection.rb +8 -0
- data/lib/sequencescape/lane.rb +8 -0
- data/lib/sequencescape/library_event.rb +5 -0
- data/lib/sequencescape/library_tube.rb +7 -0
- data/lib/sequencescape/locale/en.yml +190 -0
- data/lib/sequencescape/lot.rb +11 -0
- data/lib/sequencescape/lot_type.rb +20 -0
- data/lib/sequencescape/multiplexed_library_tube.rb +6 -0
- data/lib/sequencescape/order.rb +13 -0
- data/lib/sequencescape/order_template.rb +19 -0
- data/lib/sequencescape/pipeline.rb +10 -0
- data/lib/sequencescape/plate/pooling.rb +23 -0
- data/lib/sequencescape/plate/well_structure.rb +27 -0
- data/lib/sequencescape/plate.rb +71 -0
- data/lib/sequencescape/plate_conversion.rb +8 -0
- data/lib/sequencescape/plate_creation.rb +8 -0
- data/lib/sequencescape/plate_purpose.rb +24 -0
- data/lib/sequencescape/plate_template.rb +13 -0
- data/lib/sequencescape/pooled_plate_creation.rb +8 -0
- data/lib/sequencescape/project.rb +11 -0
- data/lib/sequencescape/qc_decision.rb +9 -0
- data/lib/sequencescape/qc_file.rb +14 -0
- data/lib/sequencescape/qcable.rb +17 -0
- data/lib/sequencescape/qcable_creator.rb +13 -0
- data/lib/sequencescape/request.rb +16 -0
- data/lib/sequencescape/robot.rb +5 -0
- data/lib/sequencescape/sample.rb +51 -0
- data/lib/sequencescape/sample_tube.rb +7 -0
- data/lib/sequencescape/search.rb +80 -0
- data/lib/sequencescape/specific_tube_creation.rb +8 -0
- data/lib/sequencescape/stamp.rb +9 -0
- data/lib/sequencescape/state_change.rb +11 -0
- data/lib/sequencescape/study.rb +14 -0
- data/lib/sequencescape/submission.rb +16 -0
- data/lib/sequencescape/submission_pool.rb +7 -0
- data/lib/sequencescape/tag.rb +14 -0
- data/lib/sequencescape/tag2_layout.rb +10 -0
- data/lib/sequencescape/tag2_layout_template.rb +9 -0
- data/lib/sequencescape/tag_group.rb +4 -0
- data/lib/sequencescape/tag_layout.rb +10 -0
- data/lib/sequencescape/tag_layout_template.rb +15 -0
- data/lib/sequencescape/template.rb +6 -0
- data/lib/sequencescape/transfer.rb +9 -0
- data/lib/sequencescape/transfer_request.rb +12 -0
- data/lib/sequencescape/transfer_request_collection.rb +8 -0
- data/lib/sequencescape/transfer_template.rb +9 -0
- data/lib/sequencescape/tube.rb +26 -0
- data/lib/sequencescape/tube_creation.rb +8 -0
- data/lib/sequencescape/tube_from_tube_creation.rb +8 -0
- data/lib/sequencescape/tube_purpose.rb +24 -0
- data/lib/sequencescape/user.rb +13 -0
- data/lib/sequencescape/volume_update.rb +6 -0
- data/lib/sequencescape/well.rb +19 -0
- data/lib/sequencescape/work_completion.rb +10 -0
- data/lib/sequencescape-api/actions.rb +69 -0
- data/lib/sequencescape-api/associations/base/instance_methods.rb +57 -0
- data/lib/sequencescape-api/associations/base.rb +15 -0
- data/lib/sequencescape-api/associations/belongs_to.rb +127 -0
- data/lib/sequencescape-api/associations/has_many/json.rb +10 -0
- data/lib/sequencescape-api/associations/has_many/validation.rb +37 -0
- data/lib/sequencescape-api/associations/has_many.rb +123 -0
- data/lib/sequencescape-api/associations.rb +112 -0
- data/lib/sequencescape-api/capabilities.rb +11 -0
- data/lib/sequencescape-api/composition.rb +37 -0
- data/lib/sequencescape-api/connection_factory/actions.rb +154 -0
- data/lib/sequencescape-api/connection_factory/helpers.rb +9 -0
- data/lib/sequencescape-api/connection_factory.rb +40 -0
- data/lib/sequencescape-api/core.rb +63 -0
- data/lib/sequencescape-api/core_ext/array.rb +7 -0
- data/lib/sequencescape-api/core_ext/hash.rb +18 -0
- data/lib/sequencescape-api/core_ext.rb +2 -0
- data/lib/sequencescape-api/error_handling.rb +42 -0
- data/lib/sequencescape-api/errors.rb +38 -0
- data/lib/sequencescape-api/finder_methods.rb +184 -0
- data/lib/sequencescape-api/locale/en.yml +7 -0
- data/lib/sequencescape-api/rails.rb +88 -0
- data/lib/sequencescape-api/resource/active_model.rb +17 -0
- data/lib/sequencescape-api/resource/attribute_groups.rb +94 -0
- data/lib/sequencescape-api/resource/attributes.rb +87 -0
- data/lib/sequencescape-api/resource/instance_methods.rb +24 -0
- data/lib/sequencescape-api/resource/json.rb +114 -0
- data/lib/sequencescape-api/resource/modifications.rb +111 -0
- data/lib/sequencescape-api/resource.rb +47 -0
- data/lib/sequencescape-api/resource_model_proxy.rb +44 -0
- data/lib/sequencescape-api/version.rb +5 -0
- data/lib/sequencescape-api.rb +16 -0
- data/lib/sequencescape.rb +81 -0
- data/sequencescape-api.gemspec +32 -0
- data/spec/sequencescape-api/associations_spec.rb +95 -0
- data/spec/sequencescape-api/contracts/belongs-to-association.txt +14 -0
- data/spec/sequencescape-api/contracts/client-fails-authentication.txt +6 -0
- data/spec/sequencescape-api/contracts/create-invalid-resource.txt +10 -0
- data/spec/sequencescape-api/contracts/create-model-c-has-many-inline-nested.txt +10 -0
- data/spec/sequencescape-api/contracts/create-model-c-has-many-inline.txt +10 -0
- data/spec/sequencescape-api/contracts/create-model-c-has-many.txt +10 -0
- data/spec/sequencescape-api/contracts/create-model-c.txt +11 -0
- data/spec/sequencescape-api/contracts/create-via-has-many.txt +9 -0
- data/spec/sequencescape-api/contracts/model-a-instance.txt +21 -0
- data/spec/sequencescape-api/contracts/model-b-instance.txt +38 -0
- data/spec/sequencescape-api/contracts/model-c-instance-created.txt +17 -0
- data/spec/sequencescape-api/contracts/model-c-instance-updated.txt +17 -0
- data/spec/sequencescape-api/contracts/model-c-instance.txt +28 -0
- data/spec/sequencescape-api/contracts/model-c-invalid-attribute.txt +8 -0
- data/spec/sequencescape-api/contracts/resource-not-found.txt +6 -0
- data/spec/sequencescape-api/contracts/results-page-1.txt +17 -0
- data/spec/sequencescape-api/contracts/results-page-2.txt +18 -0
- data/spec/sequencescape-api/contracts/results-page-3.txt +17 -0
- data/spec/sequencescape-api/contracts/retrieve-belongs-to-association.txt +3 -0
- data/spec/sequencescape-api/contracts/retrieve-model.txt +4 -0
- data/spec/sequencescape-api/contracts/retrieve-results-page-1.txt +3 -0
- data/spec/sequencescape-api/contracts/retrieve-results-page-2.txt +3 -0
- data/spec/sequencescape-api/contracts/retrieve-results-page-3.txt +3 -0
- data/spec/sequencescape-api/contracts/retrieve-root-with-an-authorised-client.txt +5 -0
- data/spec/sequencescape-api/contracts/retrieve-root-with-an-unauthorised-client.txt +4 -0
- data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-a-list.txt +4 -0
- data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-b-list.txt +4 -0
- data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-c-list.txt +4 -0
- data/spec/sequencescape-api/contracts/root-response-for-authorised-client.txt +20 -0
- data/spec/sequencescape-api/contracts/root-response-for-unauthorised-client.txt +28 -0
- data/spec/sequencescape-api/contracts/unauthorised-model-a-list.txt +15 -0
- data/spec/sequencescape-api/contracts/unauthorised-model-b-list.txt +14 -0
- data/spec/sequencescape-api/contracts/unauthorised-model-c-list.txt +15 -0
- data/spec/sequencescape-api/contracts/update-invalid-resource.txt +10 -0
- data/spec/sequencescape-api/contracts/update-model-c.txt +11 -0
- data/spec/sequencescape-api/finding_methods_spec.rb +34 -0
- data/spec/sequencescape-api/modifications_spec.rb +169 -0
- data/spec/sequencescape-api/root_spec.rb +58 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/contract_helper.rb +112 -0
- data/spec/support/namespaces.rb +67 -0
- data/spec/support/shared_examples.rb +12 -0
- metadata +540 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Sequencescape::Api::Composition
|
|
2
|
+
module Target
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
include Sequencescape::Api::Resource::ActiveModel
|
|
6
|
+
extend Sequencescape::Api::Resource::Attributes
|
|
7
|
+
|
|
8
|
+
extend Sequencescape::Api::Resource::Groups
|
|
9
|
+
include Sequencescape::Api::Resource::Groups::Json
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(owner, attributes)
|
|
14
|
+
@owner, @attributes = owner, attributes
|
|
15
|
+
attributes.each { |k,v| send(:"#{k}=", v) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :attributes
|
|
19
|
+
private :attributes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def composed_of(name, options = {})
|
|
23
|
+
composed_class_name = options[:class_name] || name
|
|
24
|
+
|
|
25
|
+
line = __LINE__ + 1
|
|
26
|
+
class_eval(%Q{
|
|
27
|
+
def #{name}
|
|
28
|
+
return nil unless attributes_for?(#{name.to_s.inspect})
|
|
29
|
+
api.model(#{composed_class_name.inspect}).new(self, attributes_for(#{name.to_s.inspect}))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def #{name}=(attributes)
|
|
33
|
+
@attributes[#{name.to_s.inspect}] = attributes
|
|
34
|
+
end
|
|
35
|
+
}, __FILE__, line)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'yajl'
|
|
3
|
+
|
|
4
|
+
# PINCHED FROM https://gist.github.com/736721
|
|
5
|
+
BEGIN {
|
|
6
|
+
require 'net/http'
|
|
7
|
+
|
|
8
|
+
Net::HTTP.module_eval do
|
|
9
|
+
alias_method '__initialize__', 'initialize'
|
|
10
|
+
|
|
11
|
+
def initialize(*args,&block)
|
|
12
|
+
__initialize__(*args, &block)
|
|
13
|
+
ensure
|
|
14
|
+
@debug_output = $stderr if ENV['HTTP_DEBUG']
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module Sequencescape::Api::ConnectionFactory::Actions
|
|
20
|
+
ServerError = Class.new(Sequencescape::Api::ConnectionFactory::ConnectionError)
|
|
21
|
+
|
|
22
|
+
def root(handler)
|
|
23
|
+
read(url, handler)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def retrieve(url, handler, content_type)
|
|
27
|
+
perform(:get, url, nil, content_type) do |response|
|
|
28
|
+
case response
|
|
29
|
+
when Net::HTTPSuccess then response
|
|
30
|
+
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
|
31
|
+
when Net::HTTPNotFound then handler.missing(parse_json_from(response))
|
|
32
|
+
when Net::HTTPRedirection then handle_redirect(response, handler)
|
|
33
|
+
else raise ServerError, response.body
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def read(url, handler)
|
|
39
|
+
perform(:get, url) do |response|
|
|
40
|
+
case response
|
|
41
|
+
when Net::HTTPSuccess then handler.success(parse_json_from(response))
|
|
42
|
+
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
|
43
|
+
when Net::HTTPNotFound then handler.missing(parse_json_from(response))
|
|
44
|
+
when Net::HTTPRedirection then handle_redirect(response, handler)
|
|
45
|
+
else raise ServerError, response.body
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create(url, body, handler)
|
|
51
|
+
perform(:post, url, jsonify(body, :action => :create)) do |response|
|
|
52
|
+
case response
|
|
53
|
+
when Net::HTTPCreated then handler.success(parse_json_from(response))
|
|
54
|
+
when Net::HTTPSuccess then handler.success(parse_json_from(response)) # TODO: should be error!
|
|
55
|
+
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
|
56
|
+
when Net::HTTPUnprocessableEntity then handler.failure(parse_json_from(response))
|
|
57
|
+
when Net::HTTPNotFound then handler.missing(parse_json_from(response))
|
|
58
|
+
when Net::HTTPRedirection then handle_redirect(response, handler)
|
|
59
|
+
else raise ServerError, response.body
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def create_from_file(url, file, filename, content_type, handler)
|
|
65
|
+
perform_for_file(:post, url, file, filename, content_type) do |response|
|
|
66
|
+
case response
|
|
67
|
+
when Net::HTTPCreated then handler.success(parse_json_from(response))
|
|
68
|
+
when Net::HTTPSuccess then handler.success(parse_json_from(response)) # TODO: should be error!
|
|
69
|
+
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
|
70
|
+
when Net::HTTPUnprocessableEntity then handler.failure(parse_json_from(response))
|
|
71
|
+
when Net::HTTPNotFound then handler.missing(parse_json_from(response))
|
|
72
|
+
when Net::HTTPRedirection then handle_redirect(response, handler)
|
|
73
|
+
else raise ServerError, response.body
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def update(url, body, handler)
|
|
79
|
+
perform(:put, url, jsonify(body, :action => :update)) do |response|
|
|
80
|
+
case response
|
|
81
|
+
when Net::HTTPSuccess then handler.success(parse_json_from(response))
|
|
82
|
+
when Net::HTTPUnauthorized then handler.unauthenticated(parse_json_from(response))
|
|
83
|
+
when Net::HTTPUnprocessableEntity then handler.failure(parse_json_from(response))
|
|
84
|
+
when Net::HTTPNotFound then handler.missing(parse_json_from(response))
|
|
85
|
+
when Net::HTTPRedirection then handle_redirect(response, handler)
|
|
86
|
+
else raise ServerError, response.body
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def handle_redirect(response, handler)
|
|
92
|
+
handler.redirection(parse_json_from(response)) do |read_handler|
|
|
93
|
+
read(response['Location'], read_handler)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
private :handle_redirect
|
|
97
|
+
|
|
98
|
+
def perform(http_verb, url, body = nil, accepts = nil, &block)
|
|
99
|
+
begin
|
|
100
|
+
uri = URI.parse(url)
|
|
101
|
+
rescue URI::InvalidURIError => e
|
|
102
|
+
raise URI::InvalidURIError, "#{http_verb} failed: #{url.inspect} is not a valid uri"
|
|
103
|
+
end
|
|
104
|
+
Net::HTTP.start(uri.host, uri.port) do |connection|
|
|
105
|
+
connection.read_timeout = read_timeout
|
|
106
|
+
request_headers = headers
|
|
107
|
+
request_headers.merge!('Accept' => accepts) unless accepts.nil?
|
|
108
|
+
request = Net::HTTP.const_get(http_verb.to_s.classify).new(uri.request_uri, request_headers)
|
|
109
|
+
unless body.nil?
|
|
110
|
+
request.content_type = 'application/json'
|
|
111
|
+
#request.body = body.to_json
|
|
112
|
+
request.body = Yajl::Encoder.encode(body)
|
|
113
|
+
end
|
|
114
|
+
yield(connection.request(request))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
private :perform
|
|
118
|
+
|
|
119
|
+
def perform_for_file(http_verb, url, file, filename, content_type, &block)
|
|
120
|
+
uri = URI.parse(url)
|
|
121
|
+
Net::HTTP.start(uri.host, uri.port) do |connection|
|
|
122
|
+
connection.read_timeout = read_timeout
|
|
123
|
+
file_headers = headers.merge!({'Content-Disposition'=> "form-data; filename=\"#{filename}\""})
|
|
124
|
+
request = Net::HTTP.const_get(http_verb.to_s.classify).new(uri.request_uri, file_headers)
|
|
125
|
+
request.content_type = content_type
|
|
126
|
+
#request.body = body.to_json
|
|
127
|
+
request.body = file.read
|
|
128
|
+
yield(connection.request(request))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
private :perform
|
|
132
|
+
|
|
133
|
+
def jsonify(body, options)
|
|
134
|
+
case
|
|
135
|
+
when body.nil? then {}
|
|
136
|
+
when body.is_a?(Hash) then body
|
|
137
|
+
else body.as_json(options.merge(:root => true))
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
private :jsonify
|
|
141
|
+
|
|
142
|
+
def parse_json_from(response)
|
|
143
|
+
raise ServerError, 'server returned non-JSON content' unless response.content_type == 'application/json'
|
|
144
|
+
Yajl::Parser.parse(StringIO.new(response.body))
|
|
145
|
+
end
|
|
146
|
+
private :parse_json_from
|
|
147
|
+
|
|
148
|
+
def headers
|
|
149
|
+
{ 'Accept' => 'application/json', 'Cookie' => "WTSISignOn=#{cookie}" }.tap do |standard|
|
|
150
|
+
standard.merge!('X-Sequencescape-Client-ID' => @authorisation) unless @authorisation.blank?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
private :headers
|
|
154
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
class Sequencescape::Api::ConnectionFactory
|
|
4
|
+
ConnectionError = Class.new(::Sequencescape::Api::Error)
|
|
5
|
+
|
|
6
|
+
class_attribute :default_url
|
|
7
|
+
|
|
8
|
+
def self.create(options)
|
|
9
|
+
required_options = []
|
|
10
|
+
required_options.push(:cookie) if options[:authorisation].blank?
|
|
11
|
+
required_options.push(:url) if self.default_url.blank?
|
|
12
|
+
|
|
13
|
+
required_options.push(:allow_blank => false)
|
|
14
|
+
options.required!(*required_options) do |missing|
|
|
15
|
+
raise ::Sequencescape::Api::Error, "No #{missing.or_sentence} set"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
options[:url] ||= self.default_url
|
|
19
|
+
new(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :url, :cookie, :read_timeout
|
|
23
|
+
private :url, :cookie, :read_timeout
|
|
24
|
+
|
|
25
|
+
def initialize(options)
|
|
26
|
+
@url, @cookie, @authorisation = options[:url], options[:cookie], options[:authorisation]
|
|
27
|
+
@read_timeout = options[:read_timeout] || 120
|
|
28
|
+
end
|
|
29
|
+
private_class_method :initialize
|
|
30
|
+
|
|
31
|
+
def url_for_uuid(uuid)
|
|
32
|
+
URI.join(url, uuid).to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
require 'sequencescape-api/connection_factory/helpers'
|
|
36
|
+
require 'sequencescape-api/connection_factory/actions'
|
|
37
|
+
|
|
38
|
+
include Actions
|
|
39
|
+
end
|
|
40
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'sequencescape-api/core_ext'
|
|
2
|
+
require 'sequencescape-api/resource_model_proxy'
|
|
3
|
+
require 'sequencescape-api/connection_factory'
|
|
4
|
+
require 'sequencescape-api/capabilities'
|
|
5
|
+
|
|
6
|
+
require 'active_support'
|
|
7
|
+
require 'active_support/core_ext/class'
|
|
8
|
+
|
|
9
|
+
class Sequencescape::Api
|
|
10
|
+
extend Sequencescape::Api::ConnectionFactory::Helpers
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@models, @model_namespace = {}, options.delete(:namespace) || Sequencescape
|
|
14
|
+
@model_namespace = @model_namespace.constantize if @model_namespace.is_a?(String)
|
|
15
|
+
@connection = self.class.connection_factory.create(options).tap do |connection|
|
|
16
|
+
connection.root(self)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :capabilities
|
|
21
|
+
delegate :read, :create, :create_from_file, :update, :retrieve, :to => :@connection
|
|
22
|
+
|
|
23
|
+
def read_uuid(uuid, handler)
|
|
24
|
+
read(@connection.url_for_uuid(uuid), handler)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def respond_to?(name, include_private = false)
|
|
28
|
+
super || @models.keys.include?(name.to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def method_missing(name, *args, &block)
|
|
32
|
+
return super unless @models.keys.include?(name.to_s)
|
|
33
|
+
ResourceModelProxy.new(self, model(name), @models[name.to_s])
|
|
34
|
+
end
|
|
35
|
+
protected :method_missing
|
|
36
|
+
|
|
37
|
+
def model(name)
|
|
38
|
+
parts = name.to_s.split('::').map(&:classify)
|
|
39
|
+
raise StandardError, "#{name.inspect} is rooted and that is not supported" if parts.first.blank?
|
|
40
|
+
parts.inject(@model_namespace) { |context, part| context.const_get(part) }
|
|
41
|
+
rescue NameError => missing_constant_in_user_specified_namespace_fallback
|
|
42
|
+
raise if @model_namespace == ::Sequencescape
|
|
43
|
+
parts.inject([ ::Sequencescape, @model_namespace ]) do |(source, dest), part|
|
|
44
|
+
const_from_source = source.const_get(part)
|
|
45
|
+
if dest.const_defined?(part)
|
|
46
|
+
[ const_from_source, dest.const_get(part) ]
|
|
47
|
+
else
|
|
48
|
+
[ const_from_source, dest.const_set(part, const_from_source) ]
|
|
49
|
+
end
|
|
50
|
+
end.last
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
include BasicErrorHandling
|
|
54
|
+
|
|
55
|
+
def success(json)
|
|
56
|
+
@capabilities = Sequencescape::Api.const_get("Version#{json.delete('revision') || 1}").new
|
|
57
|
+
@models = json.each_with_object({}) { |(k, v), models| models[k.to_s.singularize] = v['actions'] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def inspect
|
|
61
|
+
"#<Sequencescape::Api @connection=#{@connection.inspect}>"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Yields all of the missing keys if there are any so that you can do what you like, like
|
|
3
|
+
# error maybe?
|
|
4
|
+
def required!(*keys, &block)
|
|
5
|
+
options = keys.extract_options!
|
|
6
|
+
return if keys.empty?
|
|
7
|
+
|
|
8
|
+
allowance_method = (options[:allow_blank] == false) ? :blank? : :nil?
|
|
9
|
+
|
|
10
|
+
missing = keys.inject([]) do |missing, next_key|
|
|
11
|
+
missing.tap do
|
|
12
|
+
value = self[next_key]
|
|
13
|
+
missing << next_key if value.send(allowance_method)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
yield(missing) unless missing.empty?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'active_model/naming'
|
|
2
|
+
require 'active_model/translation'
|
|
3
|
+
|
|
4
|
+
# Uh, ok, so why do I have to include these if I'm not going to use them!?!!
|
|
5
|
+
require 'active_model/errors'
|
|
6
|
+
|
|
7
|
+
# Uh, ok, so why do I have to include these when I've kind of said I want everything!?!!
|
|
8
|
+
require 'active_model/validator'
|
|
9
|
+
require 'active_model/validations'
|
|
10
|
+
require 'active_model/callbacks'
|
|
11
|
+
|
|
12
|
+
module Sequencescape::Api::ErrorHandling
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.class_eval do
|
|
15
|
+
extend ActiveModel::Translation
|
|
16
|
+
include ActiveModel::Validations
|
|
17
|
+
include ActiveModel::Validations::Callbacks
|
|
18
|
+
include TurnOffValidationOfUuidOnlyRecords
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#--
|
|
23
|
+
# A bit of a fiddle this but any records that are UUID only are typically coming from the user
|
|
24
|
+
# having selected a load. If this has happened then there is no data and so none of the
|
|
25
|
+
# validations should be run.
|
|
26
|
+
#++
|
|
27
|
+
module TurnOffValidationOfUuidOnlyRecords
|
|
28
|
+
def run_validations!
|
|
29
|
+
uuid_only? ? true : super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def uuid_only=(value)
|
|
33
|
+
@uuid_only = value
|
|
34
|
+
end
|
|
35
|
+
private :uuid_only=
|
|
36
|
+
|
|
37
|
+
def uuid_only?
|
|
38
|
+
@uuid_only
|
|
39
|
+
end
|
|
40
|
+
private :uuid_only?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Sequencescape
|
|
2
|
+
class Api
|
|
3
|
+
Error = Class.new(StandardError)
|
|
4
|
+
|
|
5
|
+
module GeneralError
|
|
6
|
+
def initialize(json)
|
|
7
|
+
super(json['general'])
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
[ :UnauthenticatedError, :ResourceNotFound ].each do |name|
|
|
12
|
+
const_set(name, Class.new(Error) { |c| c.send(:include, GeneralError) })
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ResourceInvalid < Error
|
|
16
|
+
def initialize(resource)
|
|
17
|
+
super('Resource is reported as invalid by the server')
|
|
18
|
+
@resource = resource
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :resource
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module BasicErrorHandling
|
|
25
|
+
def unauthenticated(json)
|
|
26
|
+
raise Sequencescape::Api::UnauthenticatedError, json
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def missing(json)
|
|
30
|
+
raise Sequencescape::Api::ResourceNotFound, json
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def redirection(json, &block)
|
|
34
|
+
yield(self)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
|
|
3
|
+
module Sequencescape::Api::FinderMethods
|
|
4
|
+
module Delegation
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.with_options(:to => :all) do |all|
|
|
7
|
+
all.delegate :each, :first, :last, :to_a, :size
|
|
8
|
+
all.delegate :empty?, :blank?
|
|
9
|
+
all.delegate :each_page, :first_page, :last_page
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def extended(base)
|
|
16
|
+
base.singleton_class.send(:include, Delegation)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def included(base)
|
|
20
|
+
base.send(:include, Delegation)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class FindByUuidHandler
|
|
25
|
+
include Sequencescape::Api::BasicErrorHandling
|
|
26
|
+
|
|
27
|
+
def initialize(owner)
|
|
28
|
+
@owner = owner
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def new(*args, &block)
|
|
32
|
+
# TODO: Consider updating
|
|
33
|
+
@owner.__send__(:new, *args, &block)
|
|
34
|
+
end
|
|
35
|
+
private :new
|
|
36
|
+
|
|
37
|
+
def success(json)
|
|
38
|
+
new(json, true)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find(uuid)
|
|
43
|
+
api.read_uuid(uuid, FindByUuidHandler.new(self))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class AllHandler
|
|
47
|
+
include Sequencescape::Api::BasicErrorHandling
|
|
48
|
+
|
|
49
|
+
def initialize(owner)
|
|
50
|
+
@owner = owner
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def api(*args, &block)
|
|
54
|
+
# TODO: Consider updating
|
|
55
|
+
@owner.__send__(:api, *args, &block)
|
|
56
|
+
end
|
|
57
|
+
private :api
|
|
58
|
+
|
|
59
|
+
def new(*args, &block)
|
|
60
|
+
# TODO: Consider updating
|
|
61
|
+
@owner.__send__(:new, *args, &block)
|
|
62
|
+
end
|
|
63
|
+
private :new
|
|
64
|
+
|
|
65
|
+
def success(json)
|
|
66
|
+
::Sequencescape::Api::PageOfResults.new(api, json, &method(:new))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def all
|
|
71
|
+
api.read(actions.read, AllHandler.new(self))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module Sequencescape::Api::FinderMethods::Caching
|
|
76
|
+
def all(reload = false)
|
|
77
|
+
@cached_all = super() if @cached_all.nil? or reload
|
|
78
|
+
@cached_all
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class Sequencescape::Api::PageOfResults
|
|
83
|
+
include Enumerable
|
|
84
|
+
|
|
85
|
+
attr_reader :api, :actions
|
|
86
|
+
private :api, :actions
|
|
87
|
+
|
|
88
|
+
attr_reader :size
|
|
89
|
+
|
|
90
|
+
def initialize(api, json, &block)
|
|
91
|
+
@api, @ctor = api, block
|
|
92
|
+
update_from_json(json)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def empty?
|
|
96
|
+
return @size.zero? if api.capabilities.size_in_pages?
|
|
97
|
+
|
|
98
|
+
first_page
|
|
99
|
+
@objects.empty?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def present?
|
|
103
|
+
!empty?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def each(&block)
|
|
107
|
+
walk_pages do
|
|
108
|
+
@objects.each(&block)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def each_page(&block)
|
|
113
|
+
walk_pages do
|
|
114
|
+
yield(@objects.dup)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def first
|
|
119
|
+
first_page
|
|
120
|
+
@objects.first
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def last
|
|
124
|
+
last_page
|
|
125
|
+
@objects.last
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def walk_pages
|
|
129
|
+
first_page
|
|
130
|
+
while true
|
|
131
|
+
yield
|
|
132
|
+
break if actions.next.blank?
|
|
133
|
+
next_page
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
private :walk_pages
|
|
137
|
+
|
|
138
|
+
class UpdateHandler
|
|
139
|
+
include Sequencescape::Api::BasicErrorHandling
|
|
140
|
+
|
|
141
|
+
def initialize(owner)
|
|
142
|
+
@owner = owner
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def update_from_json(*args, &block)
|
|
146
|
+
# TODO: Consider updating
|
|
147
|
+
@owner.__send__(:update_from_json, *args, &block)
|
|
148
|
+
end
|
|
149
|
+
private :update_from_json
|
|
150
|
+
|
|
151
|
+
def success(json)
|
|
152
|
+
update_from_json(json)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
[ :first, :last, :previous, :next ].each do |page|
|
|
157
|
+
line = __LINE__ + 1
|
|
158
|
+
class_eval(%Q{
|
|
159
|
+
def #{page}_page
|
|
160
|
+
self.tap do
|
|
161
|
+
api.read(actions.#{page}, UpdateHandler.new(self)) unless actions.read == actions.#{page}
|
|
162
|
+
yield(@objects.dup) if block_given?
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
}, __FILE__, line)
|
|
166
|
+
end
|
|
167
|
+
private :last_page, :next_page
|
|
168
|
+
|
|
169
|
+
def update_from_json(json)
|
|
170
|
+
json.delete('uuids_to_ids') # Discard unwanted rubbish
|
|
171
|
+
actions = json.delete('actions')
|
|
172
|
+
raise Sequencescape::Api::Error, 'No actions for page!' if actions.blank?
|
|
173
|
+
|
|
174
|
+
if api.capabilities.size_in_pages?
|
|
175
|
+
size = json.delete('size')
|
|
176
|
+
raise Sequencescape::Api::Error, 'No size for page!' if size.blank?
|
|
177
|
+
@size = size.to_i
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
raise Sequencescape::Api::Error, 'No object json in page!' if json.keys.empty?
|
|
181
|
+
@actions, @objects = OpenStruct.new(actions), json[json.keys.first].map(&@ctor)
|
|
182
|
+
end
|
|
183
|
+
private :update_from_json
|
|
184
|
+
end
|