sequencescape-client-api 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- 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,88 @@
|
|
1
|
+
module Sequencescape::Api::Rails
|
2
|
+
# Including this module into your Rails ApplicationController adds a before filter that will
|
3
|
+
# provide a user (based on the WTSISignOn cookie) specific Sequencescape::Api instance to
|
4
|
+
# use, accessible through `api`.
|
5
|
+
module ApplicationController
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_reader :api
|
9
|
+
private :api
|
10
|
+
|
11
|
+
if respond_to?(:before_action)
|
12
|
+
before_action :configure_api
|
13
|
+
else
|
14
|
+
before_filter :configure_api
|
15
|
+
end
|
16
|
+
|
17
|
+
# Order is important here: later ones override earlier.
|
18
|
+
rescue_from(::Sequencescape::Api::Error, :with => :sequencescape_api_error_handler)
|
19
|
+
rescue_from(::Sequencescape::Api::UnauthenticatedError, :with => :sequencescape_api_unauthenticated_handler)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def api_class
|
24
|
+
::Sequencescape::Api
|
25
|
+
end
|
26
|
+
private :api_class
|
27
|
+
|
28
|
+
def configure_api
|
29
|
+
@api = api_class.new({ :cookie => cookies['WTSISignOn'] }.merge(api_connection_options))
|
30
|
+
end
|
31
|
+
private :configure_api
|
32
|
+
|
33
|
+
def api_connection_options
|
34
|
+
{ }
|
35
|
+
end
|
36
|
+
private :api_connection_options
|
37
|
+
|
38
|
+
def sequencescape_api_error_handler(exception)
|
39
|
+
Rails.logger.error "#{exception}, #{exception.backtrace}"
|
40
|
+
raise StandardError, "There is an issue with the API connection to Sequencescape (#{exception.message})"
|
41
|
+
end
|
42
|
+
private :sequencescape_api_error_handler
|
43
|
+
|
44
|
+
def sequencescape_api_unauthenticated_handler(exception)
|
45
|
+
Rails.logger.error "#{exception}, #{exception.backtrace}"
|
46
|
+
raise StandardError, "You are not authenticated; please visit the WTSI login page"
|
47
|
+
end
|
48
|
+
private :sequencescape_api_unauthenticated_handler
|
49
|
+
end
|
50
|
+
|
51
|
+
# Including this module into your Rails model indicates that the model is associated with
|
52
|
+
# a remote resource. This then means that your model table needs a string 'uuid' column
|
53
|
+
# and that when you perform a save the remote resource will also be saved if it can or
|
54
|
+
# needs to be. The remote resource is exposed through `remote_resource` which you are
|
55
|
+
# advised to use `delegate` to hand off to.
|
56
|
+
module Resourced
|
57
|
+
def self.included(base)
|
58
|
+
base.class_eval do
|
59
|
+
attr_protected :remote_resource, :uuid
|
60
|
+
validates_presence_of :uuid, :allow_blank => false
|
61
|
+
before_save :save_remote_resource
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def remote_resource=(resource)
|
66
|
+
@remote_resource = resource
|
67
|
+
self[:uuid] = @remote_resource.uuid
|
68
|
+
end
|
69
|
+
|
70
|
+
def uuid
|
71
|
+
self[:uuid]
|
72
|
+
end
|
73
|
+
private :uuid
|
74
|
+
|
75
|
+
def remote_resource
|
76
|
+
@remote_resource ||= api.find(uuid)
|
77
|
+
end
|
78
|
+
private :remote_resource
|
79
|
+
|
80
|
+
def save_remote_resource
|
81
|
+
return true if @remote_resource.nil?
|
82
|
+
return true unless @remote_resource.can_save?
|
83
|
+
self[:uuid] = @remote_resource
|
84
|
+
@remote_resource.save
|
85
|
+
end
|
86
|
+
private :save_remote_resource
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_model/conversion'
|
2
|
+
require 'active_model/attribute_methods'
|
3
|
+
require 'active_model/dirty'
|
4
|
+
|
5
|
+
# Code that is required to support the ActiveModel basic interface.
|
6
|
+
module Sequencescape::Api::Resource::ActiveModel
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
include ::ActiveModel::Conversion
|
10
|
+
include ::ActiveModel::Dirty
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def persisted?
|
15
|
+
not self.uuid.nil?
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'active_model/dirty'
|
2
|
+
|
3
|
+
module Sequencescape::Api::Resource::Groups
|
4
|
+
module InstanceMethods
|
5
|
+
def attribute_groups
|
6
|
+
@attribute_groups ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def attribute_group_json(options)
|
10
|
+
attribute_groups.each_with_object({}) do |(k,v), agj|
|
11
|
+
agj[k.to_s] = v.send(:as_json_for_update, options) if v.changed?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
private :attribute_group_json
|
15
|
+
|
16
|
+
def changed?
|
17
|
+
super or attribute_groups.values.any?(&:changed?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear_changed_attributes
|
21
|
+
super
|
22
|
+
attribute_groups.values.map(&:clear_changed_attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
def eager_loaded_attribute?(name)
|
26
|
+
super or attribute_groups.key?(name.to_sym)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Json
|
31
|
+
def as_json_for_update(options)
|
32
|
+
super.tap do |json|
|
33
|
+
begin
|
34
|
+
if options[:root]
|
35
|
+
json.fetch(json_root).merge!(attribute_group_json(options))
|
36
|
+
else
|
37
|
+
json.merge!(attribute_group_json(options))
|
38
|
+
end
|
39
|
+
rescue KeyError => e
|
40
|
+
# If we get a key error, append the json to out exception to assist diagnosing issues
|
41
|
+
e.message << " in #{json.to_json}"
|
42
|
+
raise e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
private :as_json_for_update
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.extended(base)
|
50
|
+
base.class_eval do
|
51
|
+
include InstanceMethods
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def attribute_group(name, &block)
|
56
|
+
proxy_class = Class.new(Proxy)
|
57
|
+
proxy_class.instance_eval(&block)
|
58
|
+
define_method(name) { attribute_groups[name.to_sym] ||= proxy_class.new(self) }
|
59
|
+
define_method("#{name}=") { |attributes| attribute_groups[name.to_sym] = proxy_class.new(self, attributes) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Sequencescape::Api::Resource::Groups::Proxy
|
64
|
+
module InstanceMethods
|
65
|
+
def self.included(base)
|
66
|
+
base.class_eval do
|
67
|
+
attr_reader :attributes
|
68
|
+
private :attributes
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(owner, attributes = {})
|
73
|
+
@owner, @attributes = owner, {}
|
74
|
+
attributes.each { |k,v| send(:"#{k}=", v) if respond_to?(:"#{k}=", :include_private_methods) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def as_json_for_update(options)
|
78
|
+
attribute_group_json(options).tap do |agj|
|
79
|
+
changes.each { |k, (_, v)| agj[k.to_s] = v }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private :as_json_for_update
|
83
|
+
|
84
|
+
def clear_changed_attributes
|
85
|
+
changed_attributes.clear
|
86
|
+
end
|
87
|
+
private :clear_changed_attributes
|
88
|
+
end
|
89
|
+
|
90
|
+
include InstanceMethods
|
91
|
+
include ::ActiveModel::Dirty
|
92
|
+
extend Sequencescape::Api::Resource::Attributes
|
93
|
+
extend Sequencescape::Api::Resource::Groups
|
94
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'active_support/core_ext/string/conversions'
|
3
|
+
|
4
|
+
module Sequencescape::Api::Resource::Attributes
|
5
|
+
def self.extended(base)
|
6
|
+
base.class_eval do
|
7
|
+
include InstanceMethods
|
8
|
+
class_attribute :defined_attributes, :instance_writer => false
|
9
|
+
self.defined_attributes = Set.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
def is_attribute?(name)
|
15
|
+
defined_attributes.include?(name.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def eager_loaded_attribute?(name)
|
19
|
+
is_attribute?(name) and attributes.key?(name.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_attribute_reader(*names)
|
24
|
+
options = names.extract_options!
|
25
|
+
conversion = options[:conversion].blank? ? nil : "try(#{options[:conversion].to_sym.inspect})"
|
26
|
+
|
27
|
+
names.each do |name|
|
28
|
+
defined_attributes << name.to_sym
|
29
|
+
converted = [ "#{name}_before_type_cast", conversion ].compact.join('.')
|
30
|
+
|
31
|
+
line = __LINE__ + 1
|
32
|
+
class_eval(%Q{
|
33
|
+
def #{name}
|
34
|
+
#{converted}
|
35
|
+
end
|
36
|
+
|
37
|
+
def #{name}_before_type_cast
|
38
|
+
attributes[#{name.to_s.inspect}]
|
39
|
+
end
|
40
|
+
}, __FILE__, line)
|
41
|
+
end
|
42
|
+
extend_attribute_methods(names)
|
43
|
+
end
|
44
|
+
private :generate_attribute_reader
|
45
|
+
|
46
|
+
def generate_attribute_writer(*names)
|
47
|
+
options = names.extract_options!
|
48
|
+
|
49
|
+
names.each do |name|
|
50
|
+
defined_attributes << name.to_sym
|
51
|
+
|
52
|
+
line = __LINE__ + 1
|
53
|
+
class_eval(%Q{
|
54
|
+
def #{name}=(value)
|
55
|
+
#{name}_will_change! if not attributes.key?(#{name.to_s.inspect}) or #{name} != value
|
56
|
+
attributes[#{name.to_s.inspect}] = value
|
57
|
+
end
|
58
|
+
}, __FILE__, line)
|
59
|
+
end
|
60
|
+
extend_attribute_methods(names)
|
61
|
+
end
|
62
|
+
private :generate_attribute_writer
|
63
|
+
|
64
|
+
def attribute_reader(*names)
|
65
|
+
attribute_accessor(*names)
|
66
|
+
class_eval { names.map { |m| private :"#{m}=" } }
|
67
|
+
end
|
68
|
+
private :attribute_reader
|
69
|
+
|
70
|
+
def attribute_writer(*names)
|
71
|
+
attribute_accessor(*names)
|
72
|
+
class_eval { names.map(&method(:private)) }
|
73
|
+
end
|
74
|
+
private :attribute_writer
|
75
|
+
|
76
|
+
def attribute_accessor(*names)
|
77
|
+
generate_attribute_reader(*names)
|
78
|
+
generate_attribute_writer(*names)
|
79
|
+
end
|
80
|
+
private :attribute_accessor
|
81
|
+
|
82
|
+
def extend_attribute_methods(names)
|
83
|
+
@attribute_methods_generated = false
|
84
|
+
define_attribute_methods(names)
|
85
|
+
end
|
86
|
+
private :extend_attribute_methods
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sequencescape::Api::Resource::InstanceMethods
|
2
|
+
def self.included(base)
|
3
|
+
base.class_eval do
|
4
|
+
attr_reader :api, :actions, :attributes, :uuid
|
5
|
+
private :api, :actions, :attributes
|
6
|
+
alias_method(:model, :class)
|
7
|
+
alias_method(:id, :uuid)
|
8
|
+
|
9
|
+
delegate :hash, :to => :uuid
|
10
|
+
delegate :read_timeout, :to => :api
|
11
|
+
|
12
|
+
attribute_accessor :created_at, :updated_at, :conversion => :to_time
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(object_or_proxy)
|
17
|
+
return false unless object_or_proxy.respond_to?(:uuid)
|
18
|
+
self.uuid.eql?(object_or_proxy.uuid)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(api, json = nil, wrapped = false)
|
22
|
+
@api, @attributes = api, {}
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Sequencescape::Api::Resource::Json
|
2
|
+
def self.included(base)
|
3
|
+
base.class_eval do
|
4
|
+
extend ClassMethods
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def self.extended(base)
|
10
|
+
base.delegate :json_root, :to => 'self.class'
|
11
|
+
end
|
12
|
+
|
13
|
+
def json_root
|
14
|
+
self.name.demodulize.underscore
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class CoercionHandler
|
19
|
+
include Sequencescape::Api::BasicErrorHandling
|
20
|
+
|
21
|
+
def initialize(api, owner)
|
22
|
+
@api, @owner = api, owner
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :api
|
26
|
+
|
27
|
+
def new(*args, &block)
|
28
|
+
# TODO: Consider updating
|
29
|
+
@owner.__send__(:new, *args, &block)
|
30
|
+
end
|
31
|
+
private :new
|
32
|
+
|
33
|
+
private :api, :new
|
34
|
+
|
35
|
+
def success(json)
|
36
|
+
new(api, json, true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Coerces the current object instance to another class.
|
41
|
+
def coerce_to(klazz)
|
42
|
+
api.read_uuid(self.uuid, CoercionHandler.new(api, klazz))
|
43
|
+
end
|
44
|
+
|
45
|
+
def as_json(options = nil)
|
46
|
+
options = { :action => :create, :root => true }.merge(options || {})
|
47
|
+
send(:"as_json_for_#{options[:action]}", options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the appropriate JSON for when we are creating a resource. If the resource already exists
|
51
|
+
# then we assume that we aren't actually being created, but are being used in the creation of another
|
52
|
+
# resource, and send our UUID. If we are not persisted then all of our attributes need to be sent,
|
53
|
+
# regardless of whether we are involved in the creation of ourselves or another resource.
|
54
|
+
def as_json_for_create(options)
|
55
|
+
persisted? ? uuid : as_json_for_update(options)
|
56
|
+
end
|
57
|
+
private :as_json_for_create
|
58
|
+
|
59
|
+
# Returns the appropriate JSON for when we are updating a resource.
|
60
|
+
def as_json_for_update(options)
|
61
|
+
if must_output_full_json?(options)
|
62
|
+
json = { }
|
63
|
+
json['uuid'] = uuid if options[:uuid] and uuid.present?
|
64
|
+
|
65
|
+
json.merge!(attributes_for_json(options))
|
66
|
+
json.merge!(associations_for_json(options.merge(:root => false)))
|
67
|
+
|
68
|
+
return json unless options[:root]
|
69
|
+
{ json_root => json }
|
70
|
+
elsif options[:root]
|
71
|
+
# We are the root element so we must output something!
|
72
|
+
{ json_root => { } }
|
73
|
+
else
|
74
|
+
# We are not a root element, we haven't been changed, so we might as well not exist!
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
private :as_json_for_update
|
79
|
+
|
80
|
+
def unwrapped_json(json)
|
81
|
+
json[(json.keys - [ 'uuids_to_ids' ]).first]
|
82
|
+
end
|
83
|
+
private :unwrapped_json
|
84
|
+
|
85
|
+
def changed?
|
86
|
+
super or associations.values.any?(&:changed?)
|
87
|
+
end
|
88
|
+
|
89
|
+
def attributes_for_json(options)
|
90
|
+
attributes_to_send_to_server(options).tap do |changed_attributes|
|
91
|
+
[ 'created_at', 'updated_at' ].map(&changed_attributes.method(:delete))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
private :attributes_for_json
|
95
|
+
|
96
|
+
def attributes_to_send_to_server(options)
|
97
|
+
return attributes if options[:force] or (options[:action] == :create)
|
98
|
+
changes.keys.each_with_object({}) { |k, attributes| attributes[k.to_s] = send(k) }
|
99
|
+
end
|
100
|
+
private :attributes_to_send_to_server
|
101
|
+
|
102
|
+
def associations_for_json(options)
|
103
|
+
associations.each_with_object({}) do |(k, v), associations|
|
104
|
+
next unless must_output_full_json?(options, v)
|
105
|
+
associations[k.to_s] = v.as_json(options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
private :associations_for_json
|
109
|
+
|
110
|
+
def must_output_full_json?(options, target = self)
|
111
|
+
options[:force] or (options[:action] == :create) or target.changed?
|
112
|
+
end
|
113
|
+
private :must_output_full_json?
|
114
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class Sequencescape::Api::ModifyingHandler
|
2
|
+
include Sequencescape::Api::BasicErrorHandling
|
3
|
+
|
4
|
+
def initialize(owner)
|
5
|
+
@owner = owner
|
6
|
+
end
|
7
|
+
|
8
|
+
def update_from_json(*args, &block)
|
9
|
+
# TODO: Consider updating
|
10
|
+
@owner.__send__(:update_from_json, *args, &block)
|
11
|
+
end
|
12
|
+
private :update_from_json
|
13
|
+
|
14
|
+
def error(field_and_errors_pair)
|
15
|
+
field, errors = field_and_errors_pair
|
16
|
+
Array(errors).each { |error| @owner.errors.add(field, error) }
|
17
|
+
end
|
18
|
+
private :error
|
19
|
+
|
20
|
+
def object_error(message)
|
21
|
+
@owner.errors.add(:base, message)
|
22
|
+
end
|
23
|
+
private :object_error
|
24
|
+
|
25
|
+
def success(json)
|
26
|
+
update_from_json(json, true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure(json)
|
30
|
+
Array(json.fetch('content', [])).map(&method(:error))
|
31
|
+
Array(json.fetch('general', [])).map(&method(:object_error))
|
32
|
+
|
33
|
+
raise Sequencescape::Api::ResourceInvalid, @owner
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Sequencescape::Api::Resource::Modifications
|
38
|
+
def initialize(api, json = nil, wrapped = false)
|
39
|
+
super
|
40
|
+
update_from_json(json, wrapped)
|
41
|
+
changed_attributes.clear
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_attributes!(attributes)
|
45
|
+
changed_attributes.clear
|
46
|
+
update_from_json(attributes, false)
|
47
|
+
modify!(:action => :update)
|
48
|
+
end
|
49
|
+
alias update! update_attributes!
|
50
|
+
|
51
|
+
def save!(options = nil)
|
52
|
+
action = persisted? ? :update : :create
|
53
|
+
modify!((options || {}).merge({ :action => action }))
|
54
|
+
end
|
55
|
+
|
56
|
+
def modify!(options)
|
57
|
+
raise Sequencescape::Api::Error, "No actions exist" if options[:url].nil? and actions.nil?
|
58
|
+
|
59
|
+
action = options[:action]
|
60
|
+
skip_json = options[:skip_json]||false
|
61
|
+
http_verb = options[:http_verb] || options[:action]
|
62
|
+
url = options[:url]
|
63
|
+
url ||= (actions.send(action) or raise Sequencescape::Api::Error, "Cannot perform #{action}")
|
64
|
+
raise Sequencescape::Api::Error, "Cannot perform modification without a URL" if url.blank?
|
65
|
+
|
66
|
+
self.tap do
|
67
|
+
run_validations! or raise Sequencescape::Api::ResourceInvalid, self
|
68
|
+
|
69
|
+
object = skip_json ? {} : self
|
70
|
+
|
71
|
+
api.send(
|
72
|
+
http_verb,
|
73
|
+
url,
|
74
|
+
object,
|
75
|
+
Sequencescape::Api::ModifyingHandler.new(self)
|
76
|
+
)
|
77
|
+
|
78
|
+
changed_attributes.clear
|
79
|
+
end
|
80
|
+
end
|
81
|
+
private :modify!
|
82
|
+
|
83
|
+
def update_from_json(json, wrapped = false)
|
84
|
+
unwrapped = (wrapped ? unwrapped_json(json) : json) || {}
|
85
|
+
unwrapped.map(&method(:update_attribute))
|
86
|
+
end
|
87
|
+
private :update_from_json
|
88
|
+
|
89
|
+
def update_attribute(name_and_value_pair)
|
90
|
+
name, value = name_and_value_pair
|
91
|
+
case
|
92
|
+
when name.to_s == 'actions' then update_actions(value)
|
93
|
+
when name.to_s == 'uuid' then @uuid = (value || @uuid)
|
94
|
+
when respond_to?(:"#{name}=", :include_private) then send(:"#{name}=", value)
|
95
|
+
else # TODO: Maybe we need debug logging in here at some point!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
private :update_attribute
|
99
|
+
|
100
|
+
def update_actions(actions_from_attributes)
|
101
|
+
actions_before_update = @actions
|
102
|
+
|
103
|
+
# We're kind of in a situation where an update of the attributes could be coming from the API
|
104
|
+
# or from the client code. We know that the API will always include 'actions' so we can assume that
|
105
|
+
# if it's set then that's what we should use, or we should use the previous actions.
|
106
|
+
#
|
107
|
+
# TODO: This isn't ideal as it's open to abuse but we can live with it for the moment.
|
108
|
+
@actions = actions_from_attributes.nil? ? actions_before_update : OpenStruct.new(actions_from_attributes)
|
109
|
+
end
|
110
|
+
private :update_actions
|
111
|
+
end
|