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,57 @@
1
+ module Sequencescape::Api::Associations::Base::InstanceMethods
2
+ def self.included(base)
3
+ base.class_eval do
4
+ class_attribute :association, :options
5
+ class_attribute :default_attributes_if_missing, :instance_writer => false
6
+
7
+ attr_reader :model
8
+ delegate :read_timeout, :to => :@owner
9
+ private :model
10
+
11
+ def api(*args, &block)
12
+ # TODO: Consider updating
13
+ @owner.__send__(:api, *args, &block)
14
+ end
15
+ private :api
16
+ end
17
+ end
18
+
19
+ def initialize(owner, json = nil)
20
+ @owner = owner
21
+ @attributes = json.nil? ? owner.attributes_for(association, default_attributes_if_missing) : attributes_from(json)
22
+ @model = api.model(options[:class_name] || association)
23
+ end
24
+
25
+ def new(*args, &block)
26
+ model.new(api, *args, &block)
27
+ end
28
+ private :new
29
+
30
+ # We can be passed several different types of information as though it was JSON:
31
+ #
32
+ # 1. It could be a string, in which case we'll assume it's a UUID
33
+ # 2. It could be an instance of the model we're expected to reference, in which case we take it's attributes
34
+ # 3. It could be an association like us that references the same model, in which case we take it's attributes
35
+ # 4. Finally it could actually be the JSON!
36
+ def attributes_from(json)
37
+ case
38
+ when json.is_a?(String) then { uuid: json, uuid_only: true }
39
+ when json.is_a?(Hash) then json
40
+ when json.respond_to?(:map) then json.map(&method(:attributes_from))
41
+ when json.is_a?(Sequencescape::Api::Resource) then json.as_json(:force => true, :action => :update, :root => false, :uuid => true)
42
+ when json.is_a?(Sequencescape::Api::Associations::Base) then json.as_json(:force => true, :action => :update)
43
+ else raise json.inspect
44
+ end
45
+ end
46
+ private :attributes_from
47
+
48
+ def proxy_present?
49
+ true
50
+ end
51
+ end
52
+
53
+ class NilClass
54
+ def proxy_present?
55
+ false
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ require 'ostruct'
2
+
3
+ class Sequencescape::Api::Associations::Base
4
+ require 'sequencescape-api/associations/base/instance_methods'
5
+
6
+ include InstanceMethods
7
+
8
+ def initialize(owner, json = nil)
9
+ super
10
+ @actions = OpenStruct.new(@attributes.delete('actions'))
11
+ end
12
+
13
+ attr_reader :actions
14
+ private :actions
15
+ end
@@ -0,0 +1,127 @@
1
+ require 'sequencescape-api/associations/base'
2
+ require 'sequencescape-api/error_handling'
3
+
4
+ module Sequencescape::Api::Associations::BelongsTo
5
+ module CommonBehaviour
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include Sequencescape::Api::ErrorHandling
9
+ end
10
+ end
11
+
12
+ def initialize(*args, &block)
13
+ super
14
+ @object = new(@attributes, false)
15
+ end
16
+
17
+ def update_from_json(json)
18
+ @object.send(:update_from_json, json, false)
19
+ end
20
+ private :update_from_json
21
+
22
+ def respond_to?(name, include_private = false)
23
+ # One of our methods, or an eager loaded attribute, or the object needs to be loaded & checked
24
+ super or is_handled_by_object_instance?(name) or object.respond_to?(name, include_private)
25
+ end
26
+
27
+ def method_missing(name, *args, &block)
28
+ target = is_handled_by_object_instance?(name) ? @object : object
29
+ target.send(name, *args, &block)
30
+ end
31
+ protected :method_missing
32
+
33
+ def is_handled_by_object_instance?(name)
34
+ return false if @object.nil?
35
+
36
+ # TODO: I really hate special cases!
37
+ case
38
+ when name.to_sym == :uuid then true
39
+ when @object.eager_loaded_attribute?(name) then true
40
+ when @object.is_association?(name) then true
41
+ else false
42
+ end
43
+ end
44
+ private :is_handled_by_object_instance?
45
+
46
+ class LoadHandler
47
+ include Sequencescape::Api::BasicErrorHandling
48
+
49
+ def initialize(owner)
50
+ @owner = owner
51
+ end
52
+
53
+ delegate :loaded, :to => :@owner
54
+ private :loaded
55
+
56
+ def new(*args, &block)
57
+ # TODO: Consider updating
58
+ @owner.__send__(:new, *args, &block)
59
+ end
60
+ private :new
61
+
62
+ def success(json)
63
+ new(json, true).tap { loaded = true }
64
+ end
65
+ end
66
+
67
+ def object
68
+ @object ||= api.read(actions.read, LoadHandler.new(self))
69
+ @object
70
+ end
71
+ private :object
72
+
73
+ def as_json(options = nil)
74
+ @object.as_json({ :root => false, :uuid => false }.reverse_merge(options || {}))
75
+ end
76
+ end
77
+
78
+ class InlineAssociationProxy < Sequencescape::Api::Associations::Base
79
+ include Sequencescape::Api::Associations::BelongsTo::CommonBehaviour
80
+ end
81
+
82
+ class AssociationProxy < Sequencescape::Api::Associations::Base
83
+ include Sequencescape::Api::Associations::BelongsTo::CommonBehaviour
84
+
85
+ self.default_attributes_if_missing = {}
86
+
87
+ def initialize(*args, &block)
88
+ super
89
+ @loaded = false
90
+ end
91
+
92
+ attr_writer :loaded
93
+ private :loaded=
94
+
95
+ def object
96
+ @object = nil unless @loaded
97
+ super
98
+ end
99
+ private :object
100
+
101
+ def uuid_only?
102
+ @object.__send__(:uuid_only?)
103
+ end
104
+
105
+ delegate :hash, to: :uuid
106
+
107
+ def eql?(other)
108
+ uuid == other.uuid
109
+ end
110
+ end
111
+
112
+ def belongs_to(association, options = {}, &block)
113
+ association = association.to_sym
114
+
115
+ proxy = Class.new(
116
+ case options[:disposition].try(:to_sym)
117
+ when :inline then InlineAssociationProxy
118
+ else AssociationProxy
119
+ end
120
+ )
121
+ proxy.association = association
122
+ proxy.options = options
123
+ proxy.instance_eval(&block) if block_given?
124
+
125
+ association_methods(association, :belongs_to, proxy)
126
+ end
127
+ end
@@ -0,0 +1,10 @@
1
+ module Sequencescape::Api::Associations::HasMany::Json
2
+ def self.included(base)
3
+ base.default_attributes_if_missing = []
4
+ end
5
+
6
+ def as_json(options = nil)
7
+ options = { :root => false, :uuid => true }.reverse_merge(options || {})
8
+ all.map { |o| o.as_json(options) }.compact
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ module Sequencescape::Api::Associations::HasMany::Validation
2
+ def run_validations!
3
+ # NOTE: Don't use all? here because it fails early and we need to see all validation errors
4
+ all.inject(true) { |state, object| object.run_validations! and state }
5
+ end
6
+
7
+ def errors
8
+ CompositeErrors.new(self)
9
+ end
10
+
11
+ class CompositeErrors
12
+ def initialize(association)
13
+ @association = association
14
+ end
15
+
16
+ def full_messages
17
+ map_errors(&:full_messages).flatten
18
+ end
19
+
20
+ def empty?
21
+ map_errors(&:empty?).all?
22
+ end
23
+
24
+ def clear
25
+ map_errors(&:clear)
26
+ end
27
+
28
+ def [](field)
29
+ map_errors { |errors| errors[field] }.flatten
30
+ end
31
+
32
+ def map_errors(&block)
33
+ @association.map(&:errors)
34
+ .map(&block)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,123 @@
1
+ require 'sequencescape-api/finder_methods'
2
+ require 'sequencescape-api/associations/base'
3
+ require 'sequencescape-api/actions'
4
+
5
+ module Sequencescape::Api::Associations::HasMany
6
+ require 'sequencescape-api/associations/has_many/json'
7
+ require 'sequencescape-api/associations/has_many/validation'
8
+
9
+ class AssociationProxy < ::Sequencescape::Api::Associations::Base
10
+ include ::Sequencescape::Api::FinderMethods
11
+ include ::Sequencescape::Api::FinderMethods::Caching
12
+ extend ::Sequencescape::Api::Actions
13
+ include ::Sequencescape::Api::Associations::HasMany::Json
14
+ include ::Sequencescape::Api::Associations::HasMany::Validation
15
+ include Enumerable
16
+
17
+ def size
18
+ return @attributes['size'] if api.capabilities.size_in_pages?
19
+ all.size
20
+ end
21
+
22
+ def empty?
23
+ return @attributes['size'].zero? if api.capabilities.size_in_pages?
24
+ all.empty?
25
+ end
26
+
27
+ def present?
28
+ !empty?
29
+ end
30
+
31
+ def initialize(owner, json = nil)
32
+ super
33
+ @cached_all = case
34
+ when json.is_a?(Array) then json.map {|js| new_from(js) }
35
+ else nil
36
+ end
37
+ end
38
+
39
+ def new_from(json)
40
+ case
41
+ when json.is_a?(String) then new(uuid: json) # We've recieved an array of strings, prob. uuids
42
+ when json.is_a?(Hash) then new(json)
43
+ else json
44
+ end
45
+ end
46
+
47
+ def update_from_json(_)
48
+ @cached_all = nil
49
+ end
50
+ end
51
+
52
+ class InlineAssociationProxy
53
+ include Enumerable
54
+ include ::Sequencescape::Api::FinderMethods::Delegation
55
+ include ::Sequencescape::Api::Associations::Base::InstanceMethods
56
+ include ::Sequencescape::Api::Associations::HasMany::Json
57
+ include ::Sequencescape::Api::Associations::HasMany::Validation
58
+
59
+ def initialize(owner, json = nil)
60
+ super
61
+ @objects =
62
+ case
63
+ when @attributes.is_a?(Array) then @attributes.map(&method(:new))
64
+ when @attributes.is_a?(Hash) then @attributes.map { |uuid, json| new(json.merge('uuid' => uuid)) }
65
+ else raise StandardError, "Cannot handle has_many JSON: #{json.inspect}"
66
+ end
67
+ end
68
+
69
+ def update_from_json(json)
70
+ case
71
+ when json.is_a?(Array) then @objects = json.map(&method(:new))
72
+ when json.is_a?(Hash) then update_objects_from_json(json)
73
+ else raise StandardError, "Cannot handle has_many JSON: #{json.inspect}"
74
+ end
75
+ end
76
+ private :update_from_json
77
+
78
+ def update_objects_from_json(json)
79
+ all.each do |object|
80
+ object.send(:update_from_json, json[object.uuid]) if json.key?(object.uuid)
81
+ end
82
+ end
83
+ private :update_objects_from_json
84
+
85
+ def find(uuid)
86
+ @objects.detect { |o| o.uuid == uuid }
87
+ end
88
+
89
+ def all
90
+ @objects
91
+ end
92
+
93
+ def each_page(&block)
94
+ yield(@objects)
95
+ end
96
+
97
+ def new(json, &block)
98
+ super(json, false, &block)
99
+ end
100
+ private :new
101
+
102
+ # We are changed if any of our objects have been changed.
103
+ def changed?
104
+ @objects.any?(&:changed?)
105
+ end
106
+ end
107
+
108
+ def has_many(association, options = {}, &block)
109
+ association = association.to_sym
110
+
111
+ proxy = Class.new(
112
+ case options[:disposition].try(:to_sym)
113
+ when :inline then InlineAssociationProxy
114
+ else AssociationProxy
115
+ end
116
+ )
117
+ proxy.association = association
118
+ proxy.options = options
119
+ proxy.instance_eval(&block) if block_given?
120
+
121
+ association_methods(association, :has_many, proxy)
122
+ end
123
+ end
@@ -0,0 +1,112 @@
1
+ require 'active_model/errors'
2
+
3
+ module Sequencescape::Api::Associations
4
+ def self.extended(base)
5
+ base.class_eval do
6
+ include InstanceMethods
7
+ extend HasMany
8
+ extend BelongsTo
9
+ end
10
+ end
11
+
12
+ def association_methods(association, type, proxy)
13
+ proxy_class_name = [ association, type, 'proxy'].join('_').classify
14
+ const_set(proxy_class_name.to_sym, proxy)
15
+
16
+ line = __LINE__ + 1
17
+ class_eval(%Q{
18
+ def #{association}(reload = false)
19
+ associations[#{association.inspect}] = nil if !!reload
20
+ associations[#{association.inspect}] ||= #{proxy_class_name}.new(self)
21
+ associations[#{association.inspect}]
22
+ end
23
+
24
+ def #{association}=(json)
25
+ association = associations[#{association.inspect}]
26
+ if association.proxy_present?
27
+ association.send(:update_from_json, json)
28
+ else
29
+ associations[#{association.inspect}] = #{proxy_class_name}.new(self, json)
30
+ end
31
+ end
32
+ private #{association.inspect}=
33
+
34
+ def #{association}?
35
+ attributes_for?(#{association.inspect})
36
+ end
37
+ }, __FILE__, line)
38
+ end
39
+ private :association_methods
40
+
41
+ module InstanceMethods
42
+ def initialize(*args, &block)
43
+ @associations, @errors = {}, nil
44
+ super
45
+ end
46
+
47
+ attr_reader :associations
48
+ private :associations
49
+
50
+ def is_association?(name)
51
+ associations.key?(name.to_sym)
52
+ end
53
+
54
+ def attributes_for(path, default_value_if_missing = nil)
55
+ attributes_from_path(path, default_value_if_missing) or
56
+ raise Sequencescape::Api::JsonError.new(path.to_s, self)
57
+ end
58
+
59
+ def attributes_for?(path)
60
+ !!attributes_from_path(path)
61
+ end
62
+
63
+ def attributes_from_path(path, default_value_if_missing = nil)
64
+ path.to_s.split('.').inject(attributes) { |k,v| k.try(:[], v) } || default_value_if_missing
65
+ end
66
+ private :attributes_from_path
67
+
68
+ def run_validations!
69
+ our_result, their_result = super, @associations.values.all?(&:run_validations!)
70
+ our_result and their_result
71
+ end
72
+
73
+ class CompositeErrors < ::ActiveModel::Errors
74
+ def [](field)
75
+ association, *subfield = field.to_s.split('.')
76
+ errors_from_association = associations[association.to_sym].try(:errors).try(:[], subfield.join('.'))
77
+ errors_from_association.blank? ? super : errors_from_association
78
+ end
79
+
80
+ def full_messages
81
+ super.concat(association_errors.map(&:full_messages)).flatten
82
+ end
83
+
84
+ def empty?
85
+ super and association_errors.all?(&:empty?)
86
+ end
87
+
88
+ def clear
89
+ association_errors.map(&:clear)
90
+ super
91
+ end
92
+
93
+ def associations(*args, &block)
94
+ # TODO: Consider updating
95
+ @base.__send__(:associations, *args, &block)
96
+ end
97
+ private :associations
98
+
99
+ def association_errors
100
+ associations.values.map(&:errors)
101
+ end
102
+ private :association_errors
103
+ end
104
+
105
+ def errors
106
+ @errors ||= CompositeErrors.new(self)
107
+ end
108
+ end
109
+ end
110
+
111
+ require 'sequencescape-api/associations/has_many'
112
+ require 'sequencescape-api/associations/belongs_to'