active-fedora 6.8.0 → 7.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (215) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +15 -5
  4. data/CONTRIBUTING.md +2 -0
  5. data/Gemfile +0 -2
  6. data/History.txt +2 -32
  7. data/README.md +143 -0
  8. data/Rakefile +5 -7
  9. data/active-fedora.gemspec +9 -9
  10. data/gemfiles/rails3.gemfile +11 -0
  11. data/gemfiles/rails4.gemfile +10 -0
  12. data/lib/active_fedora.rb +31 -4
  13. data/lib/active_fedora/association_relation.rb +18 -0
  14. data/lib/active_fedora/associations.rb +38 -171
  15. data/lib/active_fedora/associations/association.rb +163 -0
  16. data/lib/active_fedora/associations/association_scope.rb +39 -0
  17. data/lib/active_fedora/associations/belongs_to_association.rb +47 -25
  18. data/lib/active_fedora/associations/builder/association.rb +55 -0
  19. data/lib/active_fedora/associations/builder/belongs_to.rb +100 -0
  20. data/lib/active_fedora/associations/builder/collection_association.rb +56 -0
  21. data/lib/active_fedora/associations/builder/has_and_belongs_to_many.rb +30 -0
  22. data/lib/active_fedora/associations/builder/has_many.rb +63 -0
  23. data/lib/active_fedora/associations/builder/singular_association.rb +32 -0
  24. data/lib/active_fedora/associations/{association_collection.rb → collection_association.rb} +203 -53
  25. data/lib/active_fedora/associations/collection_proxy.rb +862 -0
  26. data/lib/active_fedora/associations/has_and_belongs_to_many_association.rb +35 -25
  27. data/lib/active_fedora/associations/has_many_association.rb +36 -11
  28. data/lib/active_fedora/associations/singular_association.rb +62 -0
  29. data/lib/active_fedora/attributes.rb +43 -139
  30. data/lib/active_fedora/autosave_association.rb +317 -0
  31. data/lib/active_fedora/base.rb +10 -327
  32. data/lib/active_fedora/callbacks.rb +1 -3
  33. data/lib/active_fedora/content_model.rb +16 -0
  34. data/lib/active_fedora/core.rb +151 -0
  35. data/lib/active_fedora/datastream_attribute.rb +76 -0
  36. data/lib/active_fedora/datastream_hash.rb +8 -13
  37. data/lib/active_fedora/datastreams.rb +39 -26
  38. data/lib/active_fedora/digital_object.rb +2 -2
  39. data/lib/active_fedora/fedora_attributes.rb +45 -0
  40. data/lib/active_fedora/fixture_loader.rb +1 -1
  41. data/lib/active_fedora/indexing.rb +6 -1
  42. data/lib/active_fedora/model.rb +0 -17
  43. data/lib/active_fedora/nested_attributes.rb +2 -2
  44. data/lib/active_fedora/null_relation.rb +7 -0
  45. data/lib/active_fedora/om_datastream.rb +3 -4
  46. data/lib/active_fedora/persistence.rb +41 -29
  47. data/lib/active_fedora/querying.rb +2 -163
  48. data/lib/active_fedora/rdf.rb +1 -0
  49. data/lib/active_fedora/rdf/indexing.rb +67 -0
  50. data/lib/active_fedora/rdf_datastream.rb +2 -50
  51. data/lib/active_fedora/rdf_node.rb +12 -7
  52. data/lib/active_fedora/rdf_node/term_proxy.rb +30 -21
  53. data/lib/active_fedora/rdfxml_rdf_datastream.rb +1 -1
  54. data/lib/active_fedora/reflection.rb +163 -20
  55. data/lib/active_fedora/relation.rb +33 -130
  56. data/lib/active_fedora/relation/calculations.rb +19 -0
  57. data/lib/active_fedora/relation/delegation.rb +22 -0
  58. data/lib/active_fedora/relation/finder_methods.rb +247 -0
  59. data/lib/active_fedora/relation/merger.rb +22 -0
  60. data/lib/active_fedora/relation/query_methods.rb +58 -0
  61. data/lib/active_fedora/relation/spawn_methods.rb +46 -0
  62. data/lib/active_fedora/relationship_graph.rb +0 -2
  63. data/lib/active_fedora/rels_ext_datastream.rb +1 -4
  64. data/lib/active_fedora/rubydora_connection.rb +1 -1
  65. data/lib/active_fedora/scoping.rb +20 -0
  66. data/lib/active_fedora/scoping/default.rb +38 -0
  67. data/lib/active_fedora/scoping/named.rb +32 -0
  68. data/lib/active_fedora/semantic_node.rb +54 -106
  69. data/lib/active_fedora/serialization.rb +19 -0
  70. data/lib/active_fedora/sharding.rb +58 -0
  71. data/lib/active_fedora/solr_digital_object.rb +15 -5
  72. data/lib/active_fedora/solr_instance_loader.rb +1 -1
  73. data/lib/active_fedora/solr_service.rb +1 -1
  74. data/lib/active_fedora/unsaved_digital_object.rb +6 -4
  75. data/lib/active_fedora/version.rb +1 -1
  76. data/lib/tasks/active_fedora.rake +3 -0
  77. data/lib/tasks/active_fedora_dev.rake +6 -5
  78. data/spec/config_helper.rb +14 -14
  79. data/spec/integration/associations_spec.rb +168 -455
  80. data/spec/integration/attributes_spec.rb +12 -11
  81. data/spec/integration/auditable_spec.rb +11 -11
  82. data/spec/integration/autosave_association_spec.rb +25 -0
  83. data/spec/integration/base_spec.rb +163 -163
  84. data/spec/integration/belongs_to_association_spec.rb +166 -0
  85. data/spec/integration/bug_spec.rb +7 -7
  86. data/spec/integration/collection_association_spec.rb +58 -0
  87. data/spec/integration/complex_rdf_datastream_spec.rb +88 -88
  88. data/spec/integration/datastream_collections_spec.rb +69 -69
  89. data/spec/integration/datastream_spec.rb +43 -43
  90. data/spec/integration/datastreams_spec.rb +63 -63
  91. data/spec/integration/delete_all_spec.rb +46 -39
  92. data/spec/integration/fedora_solr_sync_spec.rb +5 -5
  93. data/spec/integration/field_to_solr_name_spec.rb +34 -0
  94. data/spec/integration/full_featured_model_spec.rb +100 -101
  95. data/spec/integration/has_and_belongs_to_many_associations_spec.rb +341 -0
  96. data/spec/integration/has_many_associations_spec.rb +172 -24
  97. data/spec/integration/json_serialization_spec.rb +31 -0
  98. data/spec/integration/load_from_solr_spec.rb +48 -0
  99. data/spec/integration/model_spec.rb +35 -40
  100. data/spec/integration/nested_attribute_spec.rb +42 -43
  101. data/spec/integration/ntriples_datastream_spec.rb +131 -113
  102. data/spec/integration/om_datastream_spec.rb +67 -67
  103. data/spec/integration/persistence_spec.rb +7 -7
  104. data/spec/integration/rdf_nested_attributes_spec.rb +56 -56
  105. data/spec/integration/relation_delegation_spec.rb +26 -25
  106. data/spec/integration/relation_spec.rb +42 -0
  107. data/spec/integration/rels_ext_datastream_spec.rb +20 -20
  108. data/spec/integration/scoped_query_spec.rb +61 -51
  109. data/spec/integration/solr_instance_loader_spec.rb +5 -5
  110. data/spec/integration/solr_service_spec.rb +46 -46
  111. data/spec/samples/hydra-mods_article_datastream.rb +334 -334
  112. data/spec/samples/hydra-rights_metadata_datastream.rb +57 -57
  113. data/spec/samples/marpa-dc_datastream.rb +17 -17
  114. data/spec/samples/models/audio_record.rb +16 -16
  115. data/spec/samples/models/image.rb +2 -2
  116. data/spec/samples/models/mods_article.rb +5 -5
  117. data/spec/samples/models/oral_history.rb +18 -18
  118. data/spec/samples/models/seminar.rb +24 -24
  119. data/spec/samples/models/seminar_audio_file.rb +17 -17
  120. data/spec/samples/oral_history_sample_model.rb +21 -21
  121. data/spec/samples/special_thing.rb +14 -14
  122. data/spec/spec_helper.rb +11 -7
  123. data/spec/support/an_active_model.rb +2 -8
  124. data/spec/support/freeze_mocks.rb +12 -0
  125. data/spec/support/mock_fedora.rb +17 -16
  126. data/spec/unit/active_fedora_spec.rb +58 -60
  127. data/spec/unit/attributes_spec.rb +314 -0
  128. data/spec/unit/base_active_model_spec.rb +28 -27
  129. data/spec/unit/base_cma_spec.rb +5 -5
  130. data/spec/unit/base_datastream_management_spec.rb +27 -27
  131. data/spec/unit/base_extra_spec.rb +76 -48
  132. data/spec/unit/base_spec.rb +277 -348
  133. data/spec/unit/callback_spec.rb +18 -19
  134. data/spec/unit/code_configurator_spec.rb +17 -17
  135. data/spec/unit/config_spec.rb +8 -16
  136. data/spec/unit/content_model_spec.rb +79 -60
  137. data/spec/unit/datastream_collections_spec.rb +229 -229
  138. data/spec/unit/datastream_spec.rb +51 -63
  139. data/spec/unit/datastreams_spec.rb +87 -87
  140. data/spec/unit/file_configurator_spec.rb +217 -217
  141. data/spec/unit/has_and_belongs_to_many_collection_spec.rb +44 -25
  142. data/spec/unit/has_many_collection_spec.rb +26 -8
  143. data/spec/unit/inheritance_spec.rb +13 -12
  144. data/spec/unit/model_spec.rb +39 -45
  145. data/spec/unit/nom_datastream_spec.rb +15 -15
  146. data/spec/unit/ntriples_datastream_spec.rb +123 -118
  147. data/spec/unit/om_datastream_spec.rb +227 -233
  148. data/spec/unit/persistence_spec.rb +34 -15
  149. data/spec/unit/predicates_spec.rb +73 -73
  150. data/spec/unit/property_spec.rb +17 -9
  151. data/spec/unit/qualified_dublin_core_datastream_spec.rb +33 -33
  152. data/spec/unit/query_spec.rb +222 -198
  153. data/spec/unit/rdf_datastream_spec.rb +21 -28
  154. data/spec/unit/rdf_list_nested_attributes_spec.rb +34 -34
  155. data/spec/unit/rdf_list_spec.rb +65 -64
  156. data/spec/unit/rdf_node_spec.rb +7 -7
  157. data/spec/unit/rdf_xml_writer_spec.rb +10 -10
  158. data/spec/unit/rdfxml_rdf_datastream_spec.rb +27 -27
  159. data/spec/unit/relationship_graph_spec.rb +51 -51
  160. data/spec/unit/rels_ext_datastream_spec.rb +68 -74
  161. data/spec/unit/rspec_matchers/belong_to_associated_active_fedora_object_matcher_spec.rb +15 -15
  162. data/spec/unit/rspec_matchers/have_many_associated_active_fedora_objects_matcher_spec.rb +15 -15
  163. data/spec/unit/rspec_matchers/have_predicate_matcher_spec.rb +15 -15
  164. data/spec/unit/rspec_matchers/match_fedora_datastream_matcher_spec.rb +12 -12
  165. data/spec/unit/rubydora_connection_spec.rb +5 -5
  166. data/spec/unit/semantic_node_spec.rb +48 -107
  167. data/spec/unit/serializers_spec.rb +4 -4
  168. data/spec/unit/service_definitions_spec.rb +26 -26
  169. data/spec/unit/simple_datastream_spec.rb +17 -17
  170. data/spec/unit/solr_config_options_spec.rb +29 -28
  171. data/spec/unit/solr_digital_object_spec.rb +17 -25
  172. data/spec/unit/solr_service_spec.rb +95 -82
  173. data/spec/unit/unsaved_digital_object_spec.rb +24 -23
  174. data/spec/unit/validations_spec.rb +21 -21
  175. metadata +110 -159
  176. data/.rspec +0 -1
  177. data/.rubocop.yml +0 -1
  178. data/.rubocop_todo.yml +0 -938
  179. data/CONSOLE_GETTING_STARTED.textile +0 -1
  180. data/NOKOGIRI_DATASTREAMS.textile +0 -1
  181. data/README.textile +0 -116
  182. data/lib/active_fedora/associations/association_proxy.rb +0 -178
  183. data/lib/active_fedora/delegating.rb +0 -72
  184. data/lib/active_fedora/nokogiri_datastream.rb +0 -11
  185. data/spec/integration/delegating_spec.rb +0 -59
  186. data/spec/rails3_test_app/.gitignore +0 -4
  187. data/spec/rails3_test_app/.rspec +0 -1
  188. data/spec/rails3_test_app/Gemfile +0 -40
  189. data/spec/rails3_test_app/Rakefile +0 -7
  190. data/spec/rails3_test_app/app/controllers/application_controller.rb +0 -3
  191. data/spec/rails3_test_app/app/helpers/application_helper.rb +0 -2
  192. data/spec/rails3_test_app/app/views/layouts/application.html.erb +0 -14
  193. data/spec/rails3_test_app/config.ru +0 -4
  194. data/spec/rails3_test_app/config/application.rb +0 -42
  195. data/spec/rails3_test_app/config/boot.rb +0 -6
  196. data/spec/rails3_test_app/config/database.yml +0 -22
  197. data/spec/rails3_test_app/config/environment.rb +0 -5
  198. data/spec/rails3_test_app/config/environments/development.rb +0 -25
  199. data/spec/rails3_test_app/config/environments/production.rb +0 -49
  200. data/spec/rails3_test_app/config/environments/test.rb +0 -35
  201. data/spec/rails3_test_app/config/initializers/backtrace_silencers.rb +0 -7
  202. data/spec/rails3_test_app/config/initializers/inflections.rb +0 -10
  203. data/spec/rails3_test_app/config/initializers/mime_types.rb +0 -5
  204. data/spec/rails3_test_app/config/initializers/secret_token.rb +0 -7
  205. data/spec/rails3_test_app/config/initializers/session_store.rb +0 -8
  206. data/spec/rails3_test_app/config/locales/en.yml +0 -5
  207. data/spec/rails3_test_app/config/routes.rb +0 -58
  208. data/spec/rails3_test_app/db/seeds.rb +0 -7
  209. data/spec/rails3_test_app/run_tests +0 -3
  210. data/spec/rails3_test_app/script/rails +0 -6
  211. data/spec/rails3_test_app/spec/spec_helper.rb +0 -27
  212. data/spec/rails3_test_app/spec/unit/rails_3_init.rb +0 -15
  213. data/spec/unit/association_proxy_spec.rb +0 -12
  214. data/spec/unit/base_delegate_spec.rb +0 -197
  215. data/spec/unit/base_delegate_to_spec.rb +0 -73
@@ -0,0 +1,317 @@
1
+ module ActiveFedora
2
+ # = Active Fedora Autosave Association
3
+ #
4
+ # +AutosaveAssociation+ is a module that takes care of automatically saving
5
+ # associacted records when their parent is saved. In addition to saving, it
6
+ # also destroys any associated records that were marked for destruction.
7
+ # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
8
+ #
9
+ # Saving of the parent, its associations, and the destruction of marked
10
+ # associations, all happen inside a transaction. This should never leave the
11
+ # database in an inconsistent state.
12
+ #
13
+ # If validations for any of the associations fail, their error messages will
14
+ # be applied to the parent.
15
+ #
16
+ # Note that it also means that associations marked for destruction won't
17
+ # be destroyed directly. They will however still be marked for destruction.
18
+ #
19
+ # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
20
+ # When the <tt>:autosave</tt> option is not present new associations are saved.
21
+ #
22
+ # === One-to-many Example
23
+ #
24
+ # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
25
+ #
26
+ # class Post
27
+ # has_many :comments # :autosave option is no declared
28
+ # end
29
+ #
30
+ # post = Post.new(:title => 'ruby rocks')
31
+ # post.comments.build(:body => 'hello world')
32
+ # post.save # => saves both post and comment
33
+ #
34
+ # post = Post.create(:title => 'ruby rocks')
35
+ # post.comments.build(:body => 'hello world')
36
+ # post.save # => saves both post and comment
37
+ #
38
+ # post = Post.create(:title => 'ruby rocks')
39
+ # post.comments.create(:body => 'hello world')
40
+ # post.save # => saves both post and comment
41
+ #
42
+ # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
43
+ #
44
+ # class Post
45
+ # has_many :comments, :autosave => true
46
+ # end
47
+ #
48
+ # post = Post.create(:title => 'ruby rocks')
49
+ # post.comments.create(:body => 'hello world')
50
+ # post.comments[0].body = 'hi everyone'
51
+ # post.save # => saves both post and comment, with 'hi everyone' as body
52
+ #
53
+ # Destroying one of the associated models as part of the parent's save action
54
+ # is as simple as marking it for destruction:
55
+ #
56
+ # post.comments.last.mark_for_destruction
57
+ # post.comments.last.marked_for_destruction? # => true
58
+ # post.comments.length # => 2
59
+ #
60
+ # Note that the model is _not_ yet removed from the database:
61
+ #
62
+ # id = post.comments.last.id
63
+ # Comment.find_by_id(id).nil? # => false
64
+ #
65
+ # post.save
66
+ # post.reload.comments.length # => 1
67
+ #
68
+ # Now it _is_ removed from the database:
69
+ #
70
+ # Comment.find_by_id(id).nil? # => true
71
+ #
72
+ # === Validation
73
+ #
74
+ # Children records are validated unless <tt>:validate</tt> is +false+.
75
+ module AutosaveAssociation
76
+ extend ActiveSupport::Concern
77
+
78
+ ASSOCIATION_TYPES = %w{ HasMany BelongsTo HasAndBelongsToMany }
79
+
80
+ module AssociationBuilderExtension #:nodoc:
81
+ def self.included(base)
82
+ base.valid_options << :autosave
83
+ end
84
+
85
+ def build
86
+ reflection = super
87
+ model.send(:add_autosave_association_callbacks, reflection)
88
+ reflection
89
+ end
90
+ end
91
+
92
+ included do
93
+ ASSOCIATION_TYPES.each do |type|
94
+ Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
95
+ end
96
+ end
97
+
98
+ module ClassMethods
99
+ private
100
+
101
+ def define_non_cyclic_method(name, reflection, &block)
102
+ define_method(name) do |*args|
103
+ result = true; @_already_called ||= {}
104
+ # Loop prevention for validation of associations
105
+ unless @_already_called[[name, reflection.name]]
106
+ begin
107
+ @_already_called[[name, reflection.name]]=true
108
+ result = instance_eval(&block)
109
+ ensure
110
+ @_already_called[[name, reflection.name]]=false
111
+ end
112
+ end
113
+
114
+ result
115
+ end
116
+ end
117
+
118
+ # Adds validation and save callbacks for the association as specified by
119
+ # the +reflection+.
120
+ #
121
+ # For performance reasons, we don't check whether to validate at runtime.
122
+ # However the validation and callback methods are lazy and those methods
123
+ # get created when they are invoked for the very first time. However,
124
+ # this can change, for instance, when using nested attributes, which is
125
+ # called _after_ the association has been defined. Since we don't want
126
+ # the callbacks to get defined multiple times, there are guards that
127
+ # check if the save or validation methods have already been defined
128
+ # before actually defining them.
129
+ def add_autosave_association_callbacks(reflection)
130
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
131
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
132
+ collection = reflection.collection?
133
+
134
+ unless method_defined?(save_method)
135
+ if collection
136
+ before_save :before_save_collection_association
137
+
138
+ define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
139
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
140
+ after_create save_method
141
+ after_update save_method
142
+ else
143
+ define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
144
+ before_save save_method
145
+ end
146
+ end
147
+
148
+ if reflection.validate? && !method_defined?(validation_method)
149
+ method = (collection ? :validate_collection_association : :validate_single_association)
150
+ define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
151
+ validate validation_method
152
+ end
153
+ end
154
+ end
155
+
156
+ # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
157
+ def reload(options = nil)
158
+ @marked_for_destruction = false
159
+ super
160
+ end
161
+
162
+ # Marks this record to be destroyed as part of the parents save transaction.
163
+ # This does _not_ actually destroy the record instantly, rather child record will be destroyed
164
+ # when <tt>parent.save</tt> is called.
165
+ #
166
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
167
+ def mark_for_destruction
168
+ @marked_for_destruction = true
169
+ end
170
+
171
+ # Returns whether or not this record will be destroyed as part of the parents save transaction.
172
+ #
173
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
174
+ def marked_for_destruction?
175
+ @marked_for_destruction
176
+ end
177
+
178
+ # Returns whether or not this record has been changed in any way (including whether
179
+ # any of its nested autosave associations are likewise changed)
180
+ def changed_for_autosave?
181
+ new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
182
+ end
183
+
184
+ private
185
+
186
+ # Returns the record for an association collection that should be validated
187
+ # or saved. If +autosave+ is +false+ only new records will be returned,
188
+ # unless the parent is/was a new record itself.
189
+ def associated_records_to_validate_or_save(association, new_record, autosave)
190
+ if new_record
191
+ association && association.target
192
+ elsif autosave
193
+ association.target.find_all { |record| record.changed_for_autosave? }
194
+ else
195
+ association.target.find_all { |record| record.new_record? }
196
+ end
197
+ end
198
+
199
+ # go through nested autosave associations that are loaded in memory (without loading
200
+ # any new ones), and return true if is changed for autosave
201
+ def nested_records_changed_for_autosave?
202
+ self.class.reflect_on_all_autosave_associations.any? do |reflection|
203
+ association = association_instance_get(reflection.name)
204
+ association && Array(association.target).any? { |a| a.changed_for_autosave? }
205
+ end
206
+ end
207
+
208
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
209
+ # turned on for the association.
210
+ def validate_single_association(reflection)
211
+ association = association_instance_get(reflection.name)
212
+ record = association && association.target
213
+ association_valid?(reflection, record) if record
214
+ end
215
+
216
+ # Validate the associated records if <tt>:validate</tt> or
217
+ # <tt>:autosave</tt> is turned on for the association specified by
218
+ # +reflection+.
219
+ def validate_collection_association(reflection)
220
+ if association = association_instance_get(reflection.name)
221
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
222
+ records.each { |record| association_valid?(reflection, record) }
223
+ end
224
+ end
225
+ end
226
+
227
+ # Returns whether or not the association is valid and applies any errors to
228
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
229
+ # enabled records if they're marked_for_destruction? or destroyed.
230
+ def association_valid?(reflection, record)
231
+ return true if record.destroyed? || record.marked_for_destruction?
232
+
233
+ unless valid = record.valid?
234
+ if reflection.options[:autosave]
235
+ record.errors.each do |attribute, message|
236
+ attribute = "#{reflection.name}.#{attribute}"
237
+ errors[attribute] << message
238
+ errors[attribute].uniq!
239
+ end
240
+ else
241
+ errors.add(reflection.name)
242
+ end
243
+ end
244
+ valid
245
+ end
246
+
247
+ # Is used as a before_save callback to check while saving a collection
248
+ # association whether or not the parent was a new record before saving.
249
+ def before_save_collection_association
250
+ @new_record_before_save = new_record?
251
+ true
252
+ end
253
+
254
+ # Saves any new associated records, or all loaded autosave associations if
255
+ # <tt>:autosave</tt> is enabled on the association.
256
+ #
257
+ # In addition, it destroys all children that were marked for destruction
258
+ # with mark_for_destruction.
259
+ #
260
+ # This all happens inside a transaction, _if_ the Transactions module is included into
261
+ # ActiveFedora::Base after the AutosaveAssociation module, which it does by default.
262
+ def save_collection_association(reflection)
263
+ if association = association_instance_get(reflection.name)
264
+ autosave = reflection.options[:autosave]
265
+
266
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
267
+ records.each do |record|
268
+ next if record.destroyed?
269
+
270
+ saved = true
271
+
272
+ if autosave && record.marked_for_destruction?
273
+ association.proxy.destroy(record)
274
+ elsif autosave != false && (@new_record_before_save || record.new_record?)
275
+ if autosave
276
+ saved = association.insert_record(record, false)
277
+ else
278
+ association.insert_record(record)
279
+ end
280
+ elsif autosave
281
+ saved = record.save(:validate => false)
282
+ end
283
+
284
+ raise ActiveFedora::Rollback unless saved
285
+ end
286
+ end
287
+
288
+ # reconstruct the scope now that we know the owner's id
289
+ association.send(:construct_scope) if association.respond_to?(:construct_scope)
290
+ end
291
+ end
292
+
293
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
294
+ #
295
+ # In addition, it will destroy the association if it was marked for destruction.
296
+ def save_belongs_to_association(reflection)
297
+ association = association_instance_get(reflection.name)
298
+ record = association && association.load_target
299
+ if record && !record.destroyed?
300
+ autosave = reflection.options[:autosave]
301
+
302
+ if autosave && record.marked_for_destruction?
303
+ record.destroy
304
+ elsif autosave != false
305
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
306
+
307
+ if association.updated?
308
+ self[reflection.name.to_s + "_id"] = record.id
309
+ association.loaded!
310
+ end
311
+
312
+ saved if autosave
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
@@ -12,7 +12,7 @@ module ActiveFedora
12
12
  #
13
13
  # =The Basics
14
14
  # class Oralhistory < ActiveFedora::Base
15
- # has_metadata :name => "properties", :type => ActiveFedora::SimpleDatastream do |m|
15
+ # has_metadata "properties", type: ActiveFedora::SimpleDatastream do |m|
16
16
  # m.field "narrator", :string
17
17
  # m.field "narrator", :text
18
18
  # end
@@ -24,344 +24,27 @@ module ActiveFedora
24
24
  # Datastreams defined with +has_metadata+ are accessed via the +datastreams+ member hash.
25
25
  #
26
26
  class Base
27
+ extend ActiveModel::Naming
28
+ extend ActiveSupport::DescendantsTracker
27
29
  include SemanticNode
28
-
29
- class_attribute :fedora_connection, :profile_solr_name
30
- self.fedora_connection = {}
31
- self.profile_solr_name = ActiveFedora::SolrService.solr_name("object_profile", :displayable)
32
-
33
- delegate :state=,:label=, to: :inner_object
34
-
35
- def method_missing(name, *args)
36
- dsid = corresponding_datastream_name(name)
37
- if dsid
38
- ### Create and invoke a proxy method
39
- self.class.send :define_method, name do
40
- datastreams[dsid]
41
- end
42
- self.send(name)
43
- else
44
- super
45
- end
46
- end
47
-
48
- def new?
49
- new_object?
50
- end
51
-
52
- # Has this object been saved?
53
- def new_object?
54
- inner_object.new?
55
- end
56
-
57
- ## Required by associations
58
- def new_record?
59
- self.new_object?
60
- end
61
-
62
- def persisted?
63
- !new_object?
64
- end
65
-
66
- def mark_for_destruction
67
- @marked_for_destruction = true
68
- end
69
-
70
- def marked_for_destruction?
71
- @marked_for_destruction
72
- end
73
-
74
- # Constructor. You may supply a custom +:pid+, or we call the Fedora Rest API for the
75
- # next available Fedora pid, and mark as new object.
76
- # Also, if +attrs+ does not contain +:pid+ but does contain +:namespace+ it will pass the
77
- # +:namespace+ value to Fedora::Repository.nextid to generate the next pid available within
78
- # the given namespace.
79
- def initialize(attrs = nil)
80
- attrs = {} if attrs.nil?
81
- @association_cache = {}
82
- attributes = attrs.dup
83
- @inner_object = UnsavedDigitalObject.new(self.class, attributes.delete(:namespace), attributes.delete(:pid))
84
- self.relationships_loaded = true
85
- load_datastreams
86
-
87
- [:new_object,:create_date, :modified_date].each { |k| attributes.delete(k)}
88
- self.attributes=attributes
89
- run_callbacks :initialize
90
- end
91
-
92
- # Reloads the object from Fedora.
93
- def reload
94
- raise ActiveFedora::ObjectNotFoundError, "Can't reload an object that hasn't been saved" unless persisted?
95
- clear_association_cache
96
- init_with_object(self.class.find(self.pid, cast: false).inner_object)
97
- end
98
-
99
- # Initialize an empty model object and set the +inner_obj+
100
- # example:
101
- #
102
- # class Post < ActiveFedora::Base
103
- # has_metadata :name => "properties", :type => ActiveFedora::SimpleDatastream
104
- # end
105
- #
106
- # post = Post.allocate
107
- # post.init_with_object(DigitalObject.find(pid))
108
- # post.properties.title # => 'hello world'
109
- def init_with_object(inner_obj)
110
- @association_cache = {}
111
- @inner_object = inner_obj
112
- unless @inner_object.is_a? SolrDigitalObject
113
- @inner_object.original_class = self.class
114
- ## Replace existing unchanged datastreams with the definitions found in this class if they have a different type.
115
- ## Any datastream that is deleted here will cause a reload from fedora, so avoid it whenever possible
116
- ds_specs.keys.each do |key|
117
- if @inner_object.datastreams[key] != nil && !@inner_object.datastreams[key].content_changed? && @inner_object.datastreams[key].class != self.class.ds_specs[key][:type]
118
- @inner_object.datastreams.delete(key)
119
- end
120
- end
121
- end
122
- load_datastreams
123
- run_callbacks :find
124
- run_callbacks :initialize
125
- self
126
- end
127
-
128
- # Uses {shard_index} to find or create the rubydora connection for this pid
129
- # @param [String] pid the identifier of the object to get the connection for
130
- # @return [Rubydora::Repository] The repository that the identifier exists in.
131
- def self.connection_for_pid(pid)
132
- idx = shard_index(pid)
133
- unless fedora_connection.has_key? idx
134
- if ActiveFedora.config.sharded?
135
- fedora_connection[idx] = RubydoraConnection.new(ActiveFedora.config.credentials[idx])
136
- else
137
- fedora_connection[idx] = RubydoraConnection.new(ActiveFedora.config.credentials)
138
- end
139
- end
140
- fedora_connection[idx].connection
141
- end
142
-
143
- # This is where your sharding strategy is implemented -- it's how we figure out which shard an object will be (or was) written to.
144
- # Given a pid, it decides which shard that pid will be written to (and thus retrieved from).
145
- # For a given pid, as long as your shard configuration remains the same it will always return the same value.
146
- # If you're not using sharding, this will always return 0, meaning use the first/only Fedora Repository in your configuration.
147
- # Default strategy runs a modulo of the md5 of the pid against the number of shards.
148
- # If you want to use a different sharding strategy, override this method. Make sure that it will always return the same value for a given pid and shard configuration.
149
- #@return [Integer] the index of the shard this object is stored in
150
- def self.shard_index(pid)
151
- if ActiveFedora.config.sharded?
152
- Digest::MD5.hexdigest(pid).hex % ActiveFedora.config.credentials.length
153
- else
154
- 0
155
- end
156
- end
157
-
158
-
159
- def self.datastream_class_for_name(dsid)
160
- ds_specs[dsid] ? ds_specs[dsid].fetch(:type, ActiveFedora::Datastream) : ActiveFedora::Datastream
161
- end
162
-
163
- def clone
164
- new_object = self.class.create
165
- clone_into(new_object)
166
- end
167
-
168
- # Clone the datastreams from this object into the provided object, while preserving the pid of the provided object
169
- # @param [Base] new_object clone into this object
170
- def clone_into(new_object)
171
- rels = Nokogiri::XML( rels_ext.content)
172
- rels.xpath("//rdf:Description/@rdf:about").first.value = new_object.internal_uri
173
- new_object.rels_ext.content = rels.to_xml
174
-
175
- datastreams.each do |k, v|
176
- next if k == 'RELS-EXT'
177
- new_object.datastreams[k].content = v.content
178
- end
179
- new_object if new_object.save
180
- end
181
-
182
- ### if you are doing sharding, override this method to do something other than use a sequence
183
- # @return [String] the unique pid for a new object
184
- def self.assign_pid(obj)
185
- args = {}
186
- args[:namespace] = obj.namespace if obj.namespace
187
- # TODO: This juggling of Fedora credentials & establishing connections should be handled by
188
- # an establish_fedora_connection method,possibly wrap it all into a fedora_connection method - MZ 06-05-2012
189
- if ActiveFedora.config.sharded?
190
- credentials = ActiveFedora.config.credentials[0]
191
- else
192
- credentials = ActiveFedora.config.credentials
193
- end
194
- fedora_connection[0] ||= ActiveFedora::RubydoraConnection.new(credentials)
195
- fedora_connection[0].connection.mint(args)
196
- end
197
-
198
- def inner_object # :nodoc
199
- @inner_object
200
- end
201
-
202
- #return the pid of the Fedora Object
203
- # if there is no fedora object (loaded from solr) get the instance var
204
- # TODO make inner_object a proxy that can hold the pid
205
- def pid
206
- @inner_object.pid
207
- end
208
-
209
-
210
- def id ### Needed for the nested form helper
211
- self.pid
212
- end
213
-
214
- def to_param
215
- persisted? ? to_key.join('-') : nil
216
- end
217
-
218
- def to_key
219
- persisted? ? [pid] : nil
220
- end
221
-
222
- #return the internal fedora URI
223
- def internal_uri
224
- "info:fedora/#{pid}"
225
- end
226
-
227
- #return the owner id
228
- def owner_id
229
- Array(@inner_object.ownerId).first
230
- end
231
-
232
- def owner_id=(owner_id)
233
- @inner_object.ownerId=(owner_id)
234
- end
235
-
236
- def label
237
- Array(@inner_object.label).first
238
- end
239
-
240
- def state
241
- Array(@inner_object.state).first
242
- end
243
-
244
- #return the create_date of the inner object (unless it's a new object)
245
- def create_date
246
- if @inner_object.new?
247
- Time.now
248
- elsif @inner_object.respond_to? :createdDate
249
- Array(@inner_object.createdDate).first
250
- else
251
- @inner_object.profile['objCreateDate']
252
- end
253
- end
254
-
255
- #return the modification date of the inner object (unless it's a new object)
256
- def modified_date
257
- @inner_object.new? ? Time.now : Array(@inner_object.lastModifiedDate).first
258
- end
259
-
260
- def ==(comparison_object)
261
- comparison_object.equal?(self) ||
262
- (comparison_object.instance_of?(self.class) &&
263
- comparison_object.pid == pid &&
264
- !comparison_object.new_record?)
265
- end
266
-
267
-
268
- def pretty_pid
269
- if self.pid == UnsavedDigitalObject::PLACEHOLDER
270
- nil
271
- else
272
- self.pid
273
- end
274
- end
275
-
276
- # This method adapts the inner_object to a new ActiveFedora::Base implementation
277
- # This is intended to minimize redundant interactions with Fedora
278
- def adapt_to(klass)
279
- unless klass.ancestors.include? ActiveFedora::Base
280
- raise "Cannot adapt #{self.class.name} to #{klass.name}: Not a ActiveFedora::Base subclass"
281
- end
282
- klass.allocate.init_with_object(inner_object)
283
- end
284
-
285
- # Examines the :has_model assertions in the RELS-EXT.
286
- #
287
- # If the object is of type ActiveFedora::Base, then use the first :has_model
288
- # in the RELS-EXT for this object. Due to how the RDF writer works, this is
289
- # currently just the first alphabetical model.
290
- #
291
- # If the object was instantiated with any other class, then use that class
292
- # as the base of the type the user wants rather than casting to the first
293
- # :has_model found on the object.
294
- #
295
- # In either case, if an extended model of that initial base model of the two
296
- # cases above exists in the :has_model, then instantiate as that extended
297
- # model type instead.
298
- def adapt_to_cmodel
299
- best_model_match = self.class unless self.instance_of? ActiveFedora::Base
300
-
301
- ActiveFedora::ContentModel.known_models_for( self ).each do |model_value|
302
- # If this is of type ActiveFedora::Base, then set to the first found :has_model.
303
- best_model_match ||= model_value
304
-
305
- # If there is an inheritance structure, use the most specific case.
306
- if best_model_match > model_value
307
- best_model_match = model_value
308
- end
309
- end
310
-
311
- self.instance_of?(best_model_match) ? self : self.adapt_to(best_model_match)
312
- end
313
-
314
- # ** EXPERIMENTAL **
315
- # This method returns a new object of the same class, with the internal SolrDigitalObject
316
- # replaced with an actual DigitalObject.
317
- def reify
318
- if self.inner_object.is_a? DigitalObject
319
- raise "#{self.inspect} is already a full digital object"
320
- end
321
- self.class.find self.pid
322
- end
323
-
324
- # ** EXPERIMENTAL **
325
- # This method reinitializes a lightweight, loaded-from-solr object with an actual
326
- # DigitalObject inside.
327
- def reify!
328
- if self.inner_object.is_a? DigitalObject
329
- raise "#{self.inspect} is already a full digital object"
330
- end
331
- self.init_with_object DigitalObject.find(self.class,self.pid)
332
- end
333
-
334
- def self.pids_from_uris(uris)
335
- if uris.class == String
336
- return uris.gsub("info:fedora/", "")
337
- elsif uris.class == Array
338
- arr = []
339
- uris.each do |uri|
340
- arr << uri.gsub("info:fedora/", "")
341
- end
342
- return arr
343
- end
344
- end
345
- end
346
-
347
- Base.class_eval do
30
+ include Sharding
348
31
  include ActiveFedora::Persistence
349
- extend ActiveSupport::DescendantsTracker
350
- extend Model
32
+ include Scoping
351
33
  include Loggable
352
34
  include Indexing
353
35
  include ActiveModel::Conversion
354
36
  include Validations
355
37
  include Callbacks
356
- include Attributes
357
38
  include Datastreams
358
- extend ActiveModel::Naming
359
- include Delegating
360
39
  extend Querying
361
40
  include Associations
41
+ include AutosaveAssociation
362
42
  include NestedAttributes
363
43
  include Reflection
364
- include ActiveModel::Dirty
44
+ include Attributes
45
+ include Serialization
46
+ include Core
47
+ include FedoraAttributes
365
48
  end
366
49
 
367
50
  end