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,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
|