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.
Files changed (313) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +52 -0
  5. data/.yardoc/checksums +96 -0
  6. data/.yardoc/complete +0 -0
  7. data/.yardoc/object_types +0 -0
  8. data/.yardoc/objects/root.dat +0 -0
  9. data/.yardoc/proxy_types +0 -0
  10. data/Gemfile +4 -0
  11. data/README.markdown +4 -0
  12. data/Rakefile +2 -0
  13. data/doc/Array.html +192 -0
  14. data/doc/BaitLibrary.html +139 -0
  15. data/doc/Hash.html +242 -0
  16. data/doc/NilClass.html +214 -0
  17. data/doc/Sequencescape/Api/Actions/ClassActionHelpers.html +213 -0
  18. data/doc/Sequencescape/Api/Actions/InstanceActionHelpers.html +282 -0
  19. data/doc/Sequencescape/Api/Actions.html +197 -0
  20. data/doc/Sequencescape/Api/Associations/Base/InstanceMethods.html +352 -0
  21. data/doc/Sequencescape/Api/Associations/Base.html +237 -0
  22. data/doc/Sequencescape/Api/Associations/BelongsTo/AssociationProxy.html +332 -0
  23. data/doc/Sequencescape/Api/Associations/BelongsTo/CommonBehaviour/LoadHandler.html +279 -0
  24. data/doc/Sequencescape/Api/Associations/BelongsTo/CommonBehaviour.html +432 -0
  25. data/doc/Sequencescape/Api/Associations/BelongsTo/InlineAssociationProxy.html +173 -0
  26. data/doc/Sequencescape/Api/Associations/BelongsTo.html +212 -0
  27. data/doc/Sequencescape/Api/Associations/HasMany/AssociationProxy.html +366 -0
  28. data/doc/Sequencescape/Api/Associations/HasMany/InlineAssociationProxy.html +529 -0
  29. data/doc/Sequencescape/Api/Associations/HasMany/Json.html +250 -0
  30. data/doc/Sequencescape/Api/Associations/HasMany/Validation/CompositeErrors.html +493 -0
  31. data/doc/Sequencescape/Api/Associations/HasMany/Validation.html +245 -0
  32. data/doc/Sequencescape/Api/Associations/HasMany.html +212 -0
  33. data/doc/Sequencescape/Api/Associations/InstanceMethods/CompositeErrors.html +379 -0
  34. data/doc/Sequencescape/Api/Associations/InstanceMethods.html +496 -0
  35. data/doc/Sequencescape/Api/Associations.html +201 -0
  36. data/doc/Sequencescape/Api/BasicErrorHandling.html +371 -0
  37. data/doc/Sequencescape/Api/Composition/Target.html +262 -0
  38. data/doc/Sequencescape/Api/Composition.html +215 -0
  39. data/doc/Sequencescape/Api/ConnectionFactory/Actions.html +611 -0
  40. data/doc/Sequencescape/Api/ConnectionFactory/Helpers.html +233 -0
  41. data/doc/Sequencescape/Api/ConnectionFactory.html +321 -0
  42. data/doc/Sequencescape/Api/ErrorHandling/TurnOffValidationOfUuidOnlyRecords.html +189 -0
  43. data/doc/Sequencescape/Api/ErrorHandling.html +201 -0
  44. data/doc/Sequencescape/Api/FinderMethods/AllHandler.html +279 -0
  45. data/doc/Sequencescape/Api/FinderMethods/Caching.html +183 -0
  46. data/doc/Sequencescape/Api/FinderMethods/Delegation.html +189 -0
  47. data/doc/Sequencescape/Api/FinderMethods/FindByUuidHandler.html +279 -0
  48. data/doc/Sequencescape/Api/FinderMethods.html +364 -0
  49. data/doc/Sequencescape/Api/GeneralError.html +176 -0
  50. data/doc/Sequencescape/Api/JsonError.html +205 -0
  51. data/doc/Sequencescape/Api/ModifyingHandler.html +359 -0
  52. data/doc/Sequencescape/Api/PageOfResults/UpdateHandler.html +279 -0
  53. data/doc/Sequencescape/Api/PageOfResults.html +611 -0
  54. data/doc/Sequencescape/Api/Rails/ApplicationController.html +207 -0
  55. data/doc/Sequencescape/Api/Rails/Resourced.html +269 -0
  56. data/doc/Sequencescape/Api/Rails.html +117 -0
  57. data/doc/Sequencescape/Api/Resource/ActiveModel.html +287 -0
  58. data/doc/Sequencescape/Api/Resource/Attributes/InstanceMethods.html +272 -0
  59. data/doc/Sequencescape/Api/Resource/Attributes.html +199 -0
  60. data/doc/Sequencescape/Api/Resource/Groups/InstanceMethods.html +378 -0
  61. data/doc/Sequencescape/Api/Resource/Groups/Json.html +112 -0
  62. data/doc/Sequencescape/Api/Resource/Groups/Proxy/InstanceMethods.html +256 -0
  63. data/doc/Sequencescape/Api/Resource/Groups/Proxy.html +177 -0
  64. data/doc/Sequencescape/Api/Resource/Groups.html +270 -0
  65. data/doc/Sequencescape/Api/Resource/InstanceMethods.html +344 -0
  66. data/doc/Sequencescape/Api/Resource/Json/ClassMethods.html +243 -0
  67. data/doc/Sequencescape/Api/Resource/Json/CoercionHandler.html +279 -0
  68. data/doc/Sequencescape/Api/Resource/Json.html +405 -0
  69. data/doc/Sequencescape/Api/Resource/Modifications.html +295 -0
  70. data/doc/Sequencescape/Api/Resource.html +436 -0
  71. data/doc/Sequencescape/Api/ResourceInvalid.html +289 -0
  72. data/doc/Sequencescape/Api/ResourceModelProxy.html +444 -0
  73. data/doc/Sequencescape/Api/Version1.html +218 -0
  74. data/doc/Sequencescape/Api/Version2.html +222 -0
  75. data/doc/Sequencescape/Api.html +704 -0
  76. data/doc/Sequencescape/Asset.html +262 -0
  77. data/doc/Sequencescape/AssetAudit.html +258 -0
  78. data/doc/Sequencescape/AssetGroup.html +258 -0
  79. data/doc/Sequencescape/BaitLibraryLayout.html +258 -0
  80. data/doc/Sequencescape/BarcodePrinter.html +258 -0
  81. data/doc/Sequencescape/BarcodedAsset.html +293 -0
  82. data/doc/Sequencescape/Batch.html +327 -0
  83. data/doc/Sequencescape/Behaviour/Barcoded.html +195 -0
  84. data/doc/Sequencescape/Behaviour/Labeled.html +189 -0
  85. data/doc/Sequencescape/Behaviour/Qced/QcFile.html +244 -0
  86. data/doc/Sequencescape/Behaviour/Qced.html +199 -0
  87. data/doc/Sequencescape/Behaviour/Receptacle/Aliquot.html +258 -0
  88. data/doc/Sequencescape/Behaviour/Receptacle.html +195 -0
  89. data/doc/Sequencescape/Behaviour/StateDriven.html +205 -0
  90. data/doc/Sequencescape/Behaviour.html +130 -0
  91. data/doc/Sequencescape/BulkTransfer.html +258 -0
  92. data/doc/Sequencescape/Comment.html +258 -0
  93. data/doc/Sequencescape/Lane.html +282 -0
  94. data/doc/Sequencescape/LibraryEvent.html +258 -0
  95. data/doc/Sequencescape/LibraryTube.html +322 -0
  96. data/doc/Sequencescape/Lot.html +258 -0
  97. data/doc/Sequencescape/LotType/LotCreator.html +182 -0
  98. data/doc/Sequencescape/LotType.html +268 -0
  99. data/doc/Sequencescape/MultiplexedLibraryTube.html +326 -0
  100. data/doc/Sequencescape/Order.html +338 -0
  101. data/doc/Sequencescape/OrderTemplate/OrderCreator.html +182 -0
  102. data/doc/Sequencescape/OrderTemplate.html +268 -0
  103. data/doc/Sequencescape/Pipeline.html +258 -0
  104. data/doc/Sequencescape/Plate/CommentsCreation.html +184 -0
  105. data/doc/Sequencescape/Plate/CurrentVolumeSubstraction.html +236 -0
  106. data/doc/Sequencescape/Plate/Pooling.html +249 -0
  107. data/doc/Sequencescape/Plate/WellStructure.html +313 -0
  108. data/doc/Sequencescape/Plate.html +446 -0
  109. data/doc/Sequencescape/PlateConversion.html +258 -0
  110. data/doc/Sequencescape/PlateCreation.html +258 -0
  111. data/doc/Sequencescape/PlatePurpose/PlateCreation.html +186 -0
  112. data/doc/Sequencescape/PlatePurpose.html +268 -0
  113. data/doc/Sequencescape/PlateTemplate.html +282 -0
  114. data/doc/Sequencescape/PooledPlateCreation.html +258 -0
  115. data/doc/Sequencescape/Project.html +258 -0
  116. data/doc/Sequencescape/QcDecision.html +258 -0
  117. data/doc/Sequencescape/QcFile.html +327 -0
  118. data/doc/Sequencescape/Qcable.html +258 -0
  119. data/doc/Sequencescape/QcableCreator.html +258 -0
  120. data/doc/Sequencescape/Request.html +258 -0
  121. data/doc/Sequencescape/Robot.html +258 -0
  122. data/doc/Sequencescape/Sample.html +258 -0
  123. data/doc/Sequencescape/SampleTube.html +318 -0
  124. data/doc/Sequencescape/Search/BaseHandler.html +234 -0
  125. data/doc/Sequencescape/Search/MultipleResultHandler.html +276 -0
  126. data/doc/Sequencescape/Search/SingleResultHandler.html +303 -0
  127. data/doc/Sequencescape/Search.html +431 -0
  128. data/doc/Sequencescape/SpecificTubeCreation.html +258 -0
  129. data/doc/Sequencescape/Stamp.html +258 -0
  130. data/doc/Sequencescape/StateChange.html +258 -0
  131. data/doc/Sequencescape/Study.html +258 -0
  132. data/doc/Sequencescape/Submission.html +258 -0
  133. data/doc/Sequencescape/SubmissionPool.html +258 -0
  134. data/doc/Sequencescape/Tag/Group.html +219 -0
  135. data/doc/Sequencescape/Tag.html +149 -0
  136. data/doc/Sequencescape/Tag2Layout.html +258 -0
  137. data/doc/Sequencescape/Tag2LayoutTemplate.html +258 -0
  138. data/doc/Sequencescape/TagGroup.html +258 -0
  139. data/doc/Sequencescape/TagLayout.html +258 -0
  140. data/doc/Sequencescape/TagLayoutTemplate.html +258 -0
  141. data/doc/Sequencescape/Template.html +258 -0
  142. data/doc/Sequencescape/Transfer.html +258 -0
  143. data/doc/Sequencescape/TransferTemplate.html +258 -0
  144. data/doc/Sequencescape/Tube.html +319 -0
  145. data/doc/Sequencescape/TubeCreation.html +258 -0
  146. data/doc/Sequencescape/TubeFromTubeCreation.html +258 -0
  147. data/doc/Sequencescape/TubePurpose/TubeCreation.html +186 -0
  148. data/doc/Sequencescape/TubePurpose.html +268 -0
  149. data/doc/Sequencescape/User.html +258 -0
  150. data/doc/Sequencescape/VolumeUpdate.html +258 -0
  151. data/doc/Sequencescape/Well.html +362 -0
  152. data/doc/Sequencescape.html +121 -0
  153. data/doc/_index.html +1248 -0
  154. data/doc/class_list.html +51 -0
  155. data/doc/css/common.css +1 -0
  156. data/doc/css/full_list.css +58 -0
  157. data/doc/css/style.css +481 -0
  158. data/doc/file.README.html +76 -0
  159. data/doc/file_list.html +56 -0
  160. data/doc/frames.html +17 -0
  161. data/doc/index.html +76 -0
  162. data/doc/js/app.js +243 -0
  163. data/doc/js/full_list.js +216 -0
  164. data/doc/js/jquery.js +4 -0
  165. data/doc/method_list.html +1411 -0
  166. data/doc/top-level-namespace.html +114 -0
  167. data/lib/sequencescape/asset.rb +10 -0
  168. data/lib/sequencescape/asset_audit.rb +7 -0
  169. data/lib/sequencescape/asset_group.rb +9 -0
  170. data/lib/sequencescape/bait_library.rb +14 -0
  171. data/lib/sequencescape/bait_library_layout.rb +9 -0
  172. data/lib/sequencescape/barcode_printer.rb +13 -0
  173. data/lib/sequencescape/barcoded_asset.rb +12 -0
  174. data/lib/sequencescape/batch.rb +24 -0
  175. data/lib/sequencescape/behaviour/barcoded.rb +15 -0
  176. data/lib/sequencescape/behaviour/labeled.rb +14 -0
  177. data/lib/sequencescape/behaviour/qced.rb +31 -0
  178. data/lib/sequencescape/behaviour/receptacle.rb +21 -0
  179. data/lib/sequencescape/behaviour/state_driven.rb +19 -0
  180. data/lib/sequencescape/bulk_transfer.rb +8 -0
  181. data/lib/sequencescape/comment.rb +7 -0
  182. data/lib/sequencescape/custom_metadatum_collection.rb +8 -0
  183. data/lib/sequencescape/lane.rb +8 -0
  184. data/lib/sequencescape/library_event.rb +5 -0
  185. data/lib/sequencescape/library_tube.rb +7 -0
  186. data/lib/sequencescape/locale/en.yml +190 -0
  187. data/lib/sequencescape/lot.rb +11 -0
  188. data/lib/sequencescape/lot_type.rb +20 -0
  189. data/lib/sequencescape/multiplexed_library_tube.rb +6 -0
  190. data/lib/sequencescape/order.rb +13 -0
  191. data/lib/sequencescape/order_template.rb +19 -0
  192. data/lib/sequencescape/pipeline.rb +10 -0
  193. data/lib/sequencescape/plate/pooling.rb +23 -0
  194. data/lib/sequencescape/plate/well_structure.rb +27 -0
  195. data/lib/sequencescape/plate.rb +71 -0
  196. data/lib/sequencescape/plate_conversion.rb +8 -0
  197. data/lib/sequencescape/plate_creation.rb +8 -0
  198. data/lib/sequencescape/plate_purpose.rb +24 -0
  199. data/lib/sequencescape/plate_template.rb +13 -0
  200. data/lib/sequencescape/pooled_plate_creation.rb +8 -0
  201. data/lib/sequencescape/project.rb +11 -0
  202. data/lib/sequencescape/qc_decision.rb +9 -0
  203. data/lib/sequencescape/qc_file.rb +14 -0
  204. data/lib/sequencescape/qcable.rb +17 -0
  205. data/lib/sequencescape/qcable_creator.rb +13 -0
  206. data/lib/sequencescape/request.rb +16 -0
  207. data/lib/sequencescape/robot.rb +5 -0
  208. data/lib/sequencescape/sample.rb +51 -0
  209. data/lib/sequencescape/sample_tube.rb +7 -0
  210. data/lib/sequencescape/search.rb +80 -0
  211. data/lib/sequencescape/specific_tube_creation.rb +8 -0
  212. data/lib/sequencescape/stamp.rb +9 -0
  213. data/lib/sequencescape/state_change.rb +11 -0
  214. data/lib/sequencescape/study.rb +14 -0
  215. data/lib/sequencescape/submission.rb +16 -0
  216. data/lib/sequencescape/submission_pool.rb +7 -0
  217. data/lib/sequencescape/tag.rb +14 -0
  218. data/lib/sequencescape/tag2_layout.rb +10 -0
  219. data/lib/sequencescape/tag2_layout_template.rb +9 -0
  220. data/lib/sequencescape/tag_group.rb +4 -0
  221. data/lib/sequencescape/tag_layout.rb +10 -0
  222. data/lib/sequencescape/tag_layout_template.rb +15 -0
  223. data/lib/sequencescape/template.rb +6 -0
  224. data/lib/sequencescape/transfer.rb +9 -0
  225. data/lib/sequencescape/transfer_request.rb +12 -0
  226. data/lib/sequencescape/transfer_request_collection.rb +8 -0
  227. data/lib/sequencescape/transfer_template.rb +9 -0
  228. data/lib/sequencescape/tube.rb +26 -0
  229. data/lib/sequencescape/tube_creation.rb +8 -0
  230. data/lib/sequencescape/tube_from_tube_creation.rb +8 -0
  231. data/lib/sequencescape/tube_purpose.rb +24 -0
  232. data/lib/sequencescape/user.rb +13 -0
  233. data/lib/sequencescape/volume_update.rb +6 -0
  234. data/lib/sequencescape/well.rb +19 -0
  235. data/lib/sequencescape/work_completion.rb +10 -0
  236. data/lib/sequencescape-api/actions.rb +69 -0
  237. data/lib/sequencescape-api/associations/base/instance_methods.rb +57 -0
  238. data/lib/sequencescape-api/associations/base.rb +15 -0
  239. data/lib/sequencescape-api/associations/belongs_to.rb +127 -0
  240. data/lib/sequencescape-api/associations/has_many/json.rb +10 -0
  241. data/lib/sequencescape-api/associations/has_many/validation.rb +37 -0
  242. data/lib/sequencescape-api/associations/has_many.rb +123 -0
  243. data/lib/sequencescape-api/associations.rb +112 -0
  244. data/lib/sequencescape-api/capabilities.rb +11 -0
  245. data/lib/sequencescape-api/composition.rb +37 -0
  246. data/lib/sequencescape-api/connection_factory/actions.rb +154 -0
  247. data/lib/sequencescape-api/connection_factory/helpers.rb +9 -0
  248. data/lib/sequencescape-api/connection_factory.rb +40 -0
  249. data/lib/sequencescape-api/core.rb +63 -0
  250. data/lib/sequencescape-api/core_ext/array.rb +7 -0
  251. data/lib/sequencescape-api/core_ext/hash.rb +18 -0
  252. data/lib/sequencescape-api/core_ext.rb +2 -0
  253. data/lib/sequencescape-api/error_handling.rb +42 -0
  254. data/lib/sequencescape-api/errors.rb +38 -0
  255. data/lib/sequencescape-api/finder_methods.rb +184 -0
  256. data/lib/sequencescape-api/locale/en.yml +7 -0
  257. data/lib/sequencescape-api/rails.rb +88 -0
  258. data/lib/sequencescape-api/resource/active_model.rb +17 -0
  259. data/lib/sequencescape-api/resource/attribute_groups.rb +94 -0
  260. data/lib/sequencescape-api/resource/attributes.rb +87 -0
  261. data/lib/sequencescape-api/resource/instance_methods.rb +24 -0
  262. data/lib/sequencescape-api/resource/json.rb +114 -0
  263. data/lib/sequencescape-api/resource/modifications.rb +111 -0
  264. data/lib/sequencescape-api/resource.rb +47 -0
  265. data/lib/sequencescape-api/resource_model_proxy.rb +44 -0
  266. data/lib/sequencescape-api/version.rb +5 -0
  267. data/lib/sequencescape-api.rb +16 -0
  268. data/lib/sequencescape.rb +81 -0
  269. data/sequencescape-api.gemspec +32 -0
  270. data/spec/sequencescape-api/associations_spec.rb +95 -0
  271. data/spec/sequencescape-api/contracts/belongs-to-association.txt +14 -0
  272. data/spec/sequencescape-api/contracts/client-fails-authentication.txt +6 -0
  273. data/spec/sequencescape-api/contracts/create-invalid-resource.txt +10 -0
  274. data/spec/sequencescape-api/contracts/create-model-c-has-many-inline-nested.txt +10 -0
  275. data/spec/sequencescape-api/contracts/create-model-c-has-many-inline.txt +10 -0
  276. data/spec/sequencescape-api/contracts/create-model-c-has-many.txt +10 -0
  277. data/spec/sequencescape-api/contracts/create-model-c.txt +11 -0
  278. data/spec/sequencescape-api/contracts/create-via-has-many.txt +9 -0
  279. data/spec/sequencescape-api/contracts/model-a-instance.txt +21 -0
  280. data/spec/sequencescape-api/contracts/model-b-instance.txt +38 -0
  281. data/spec/sequencescape-api/contracts/model-c-instance-created.txt +17 -0
  282. data/spec/sequencescape-api/contracts/model-c-instance-updated.txt +17 -0
  283. data/spec/sequencescape-api/contracts/model-c-instance.txt +28 -0
  284. data/spec/sequencescape-api/contracts/model-c-invalid-attribute.txt +8 -0
  285. data/spec/sequencescape-api/contracts/resource-not-found.txt +6 -0
  286. data/spec/sequencescape-api/contracts/results-page-1.txt +17 -0
  287. data/spec/sequencescape-api/contracts/results-page-2.txt +18 -0
  288. data/spec/sequencescape-api/contracts/results-page-3.txt +17 -0
  289. data/spec/sequencescape-api/contracts/retrieve-belongs-to-association.txt +3 -0
  290. data/spec/sequencescape-api/contracts/retrieve-model.txt +4 -0
  291. data/spec/sequencescape-api/contracts/retrieve-results-page-1.txt +3 -0
  292. data/spec/sequencescape-api/contracts/retrieve-results-page-2.txt +3 -0
  293. data/spec/sequencescape-api/contracts/retrieve-results-page-3.txt +3 -0
  294. data/spec/sequencescape-api/contracts/retrieve-root-with-an-authorised-client.txt +5 -0
  295. data/spec/sequencescape-api/contracts/retrieve-root-with-an-unauthorised-client.txt +4 -0
  296. data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-a-list.txt +4 -0
  297. data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-b-list.txt +4 -0
  298. data/spec/sequencescape-api/contracts/retrieve-unauthorised-model-c-list.txt +4 -0
  299. data/spec/sequencescape-api/contracts/root-response-for-authorised-client.txt +20 -0
  300. data/spec/sequencescape-api/contracts/root-response-for-unauthorised-client.txt +28 -0
  301. data/spec/sequencescape-api/contracts/unauthorised-model-a-list.txt +15 -0
  302. data/spec/sequencescape-api/contracts/unauthorised-model-b-list.txt +14 -0
  303. data/spec/sequencescape-api/contracts/unauthorised-model-c-list.txt +15 -0
  304. data/spec/sequencescape-api/contracts/update-invalid-resource.txt +10 -0
  305. data/spec/sequencescape-api/contracts/update-model-c.txt +11 -0
  306. data/spec/sequencescape-api/finding_methods_spec.rb +34 -0
  307. data/spec/sequencescape-api/modifications_spec.rb +169 -0
  308. data/spec/sequencescape-api/root_spec.rb +58 -0
  309. data/spec/spec_helper.rb +13 -0
  310. data/spec/support/contract_helper.rb +112 -0
  311. data/spec/support/namespaces.rb +67 -0
  312. data/spec/support/shared_examples.rb +12 -0
  313. metadata +540 -0
@@ -0,0 +1,7 @@
1
+ en:
2
+ activemodel:
3
+ attributes:
4
+ sequencescape:
5
+ api:
6
+ resource:
7
+ uuid: "UUID"
@@ -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