active-fedora 7.0.0.rc1 → 7.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 155ad613119b5eb0ce0333d32c7bb0fb67009847
4
- data.tar.gz: d02a5f3a7617168e35a90ff28dec878ef8ec46cf
3
+ metadata.gz: 7a37149e295c1dc3110b4fc70371e8bb25f980a8
4
+ data.tar.gz: b66fcc86fa8132818a1b40e8b1e9f06588d5d2a4
5
5
  SHA512:
6
- metadata.gz: 0ce6042eaaeca85fc1d000a6fdb593e8ef30cf7fd614e5f9a0a973fa5e33ff3b3a4c9d85ad02e2e9f4d5be3a7742d63f3634181842910aad93efb5d223557bbd
7
- data.tar.gz: 798590c4f7b93fe8b3cde0149162806bf7f6da20cbac133bdf11eb17ef18fce5cb3004a149c68243e29e3600b3c6ff1fbca9ddf126af320b1c6da4be1ac25702
6
+ metadata.gz: 2c50f3f783e44031480f8f284e6bb3c18ec480e391785cfb000e03856b047bdc0d0aa45b481e30500a2f98b23a571e75b7922080edd4085f7abbb4d72675704a
7
+ data.tar.gz: 8b70aa1d294dc6768a4e9e25c2d39835d6a73ff0e2ec7cce6101ca314837c7c87c8bc6366f3f909eb70d3a5c87131de96bf35d585740d43988a505b1e030f655
@@ -40,6 +40,18 @@ module ActiveFedora
40
40
  array_setter(key, value)
41
41
  end
42
42
 
43
+ # @return [Boolean] true if there is an reader method and it returns a
44
+ # value different from the new_value.
45
+ def value_has_changed?(field, new_value)
46
+ new_value != array_reader(field)
47
+ end
48
+
49
+ def mark_as_changed(field)
50
+ self.send("#{field}_will_change!")
51
+ end
52
+
53
+
54
+
43
55
  protected
44
56
 
45
57
  # override activemodel so it doesn't trigger a load of all the attributes.
@@ -57,11 +69,8 @@ module ActiveFedora
57
69
  return association.id_reader if association
58
70
  end
59
71
  raise UnknownAttributeError, "#{self.class} does not have an attribute `#{field}'" unless self.class.defined_attributes.key?(field)
60
- if args.present?
61
- instance_exec(*args, &self.class.defined_attributes[field].reader)
62
- else
63
- instance_exec &self.class.defined_attributes[field].reader
64
- end
72
+
73
+ self.class.defined_attributes[field].reader(self, *args)
65
74
  end
66
75
 
67
76
  def array_setter(field, args)
@@ -71,21 +80,7 @@ module ActiveFedora
71
80
  return association.id_writer(args) if association
72
81
  end
73
82
  raise UnknownAttributeError, "#{self.class} does not have an attribute `#{field}'" unless self.class.defined_attributes.key?(field)
74
- instance_exec(args, &self.class.defined_attributes[field].writer)
75
- end
76
-
77
- # @return [Boolean] true if there is an reader method and it returns a
78
- # value different from the new_value.
79
- def value_has_changed?(field, new_value)
80
- new_value != array_reader(field)
81
- end
82
-
83
- def mark_as_changed(field)
84
- self.send("#{field}_will_change!")
85
- end
86
-
87
- def datastream_for_attribute(dsid)
88
- datastreams[dsid] || raise(ArgumentError, "Undefined datastream id: `#{dsid}' in has_attributes")
83
+ self.class.defined_attributes[field].writer(self, args)
89
84
  end
90
85
 
91
86
  module ClassMethods
@@ -35,10 +35,6 @@ module ActiveFedora
35
35
  false
36
36
  end
37
37
 
38
- def validate_content_present
39
- has_content?
40
- end
41
-
42
38
  def save
43
39
  super
44
40
  self
@@ -49,6 +45,15 @@ module ActiveFedora
49
45
  self
50
46
  end
51
47
 
48
+ # Freeze datastreams such that they can be loaded from Fedora, but can't be changed
49
+ def freeze
50
+ @frozen = true
51
+ end
52
+
53
+ def frozen?
54
+ !!@frozen
55
+ end
56
+
52
57
  # serializes any changed data into the content field
53
58
  def serialize!
54
59
  end
@@ -2,7 +2,7 @@ module ActiveFedora
2
2
  # Represents the mapping between a model attribute and a field in a datastream
3
3
  class DatastreamAttribute
4
4
 
5
- attr_accessor :dsid, :field, :datastream_class, :at, :reader, :writer, :multiple
5
+ attr_accessor :dsid, :field, :datastream_class, :at, :multiple
6
6
 
7
7
  def initialize(field, dsid, datastream_class, args={})
8
8
  self.field = field
@@ -10,9 +10,6 @@ module ActiveFedora
10
10
  self.datastream_class = datastream_class
11
11
  self.multiple = args[:multiple].nil? ? false : args[:multiple]
12
12
  self.at = args[:at]
13
-
14
- initialize_reader!
15
- initialize_writer!
16
13
  end
17
14
 
18
15
  # Gives the primary solr name for a column. If there is more than one indexer on the field definition, it gives the first
@@ -33,48 +30,46 @@ module ActiveFedora
33
30
  end
34
31
  end
35
32
 
36
- private
37
-
38
- def initialize_writer!
39
- this = self
40
- self.writer = lambda do |v|
41
- ds = datastream_for_attribute(this.dsid)
42
- mark_as_changed(this.field) if value_has_changed?(this.field, v)
43
- if ds.kind_of?(ActiveFedora::RDFDatastream)
44
- ds.send("#{this.field}=", v)
45
- else
46
- terminology = this.at || [this.field]
47
- ds.send(:update_indexed_attributes, {terminology => v})
48
- end
33
+ def writer(obj, v)
34
+ ds = datastream_for_attribute(obj, dsid)
35
+ obj.mark_as_changed(field) if obj.value_has_changed?(field, v)
36
+ if ds.kind_of?(ActiveFedora::RDFDatastream)
37
+ ds.send("#{field}=", v)
38
+ else
39
+ terminology = at || [field]
40
+ ds.send(:update_indexed_attributes, {terminology => v})
49
41
  end
50
42
  end
51
43
 
52
- def initialize_reader!
53
- this = self
54
- self.reader = lambda do |*opts|
55
- if inner_object.is_a? SolrDigitalObject
56
- begin
57
- # Look in the cache
58
- return inner_object.fetch(this.field)
59
- rescue NoMethodError => e
60
- # couldn't get it from solr, so try from fedora.
61
- logger.info "Couldn't get #{this.field} out of solr, because #{e.message}. Trying another way."
62
- end
44
+ def reader(obj, *opts)
45
+ if obj.inner_object.is_a? SolrDigitalObject
46
+ begin
47
+ # Look in the cache
48
+ return obj.inner_object.fetch(field)
49
+ rescue NoMethodError => e
50
+ # couldn't get it from solr, so try from fedora.
51
+ logger.info "Couldn't get #{field} out of solr, because #{e.message}. Trying another way."
63
52
  end
64
- # Load from fedora
65
- ds = datastream_for_attribute(this.dsid)
66
- if ds.kind_of?(ActiveFedora::RDFDatastream)
67
- ds.send(this.field)
53
+ end
54
+ # Load from fedora
55
+ ds = datastream_for_attribute(obj, dsid)
56
+ if ds.kind_of?(ActiveFedora::RDFDatastream)
57
+ ds.send(field)
58
+ else
59
+ terminology = at || [field]
60
+ if terminology.length == 1 && opts.present?
61
+ ds.send(terminology.first, *opts)
68
62
  else
69
- terminology = this.at || [this.field]
70
- if terminology.length == 1 && opts.present?
71
- ds.send(terminology.first, *opts)
72
- else
73
- ds.send(:term_values, *terminology)
74
- end
63
+ ds.send(:term_values, *terminology)
75
64
  end
76
65
  end
77
66
  end
78
67
 
68
+ private
69
+
70
+ def datastream_for_attribute(obj, dsid)
71
+ obj.datastreams[dsid] || raise(ArgumentError, "Undefined datastream id: `#{dsid}' in has_attributes")
72
+ end
73
+
79
74
  end
80
75
  end
@@ -20,8 +20,8 @@ module ActiveFedora
20
20
  end
21
21
 
22
22
  def freeze
23
- each do |k, v|
24
- v.freeze
23
+ each_value do |datastream|
24
+ datastream.freeze
25
25
  end
26
26
  super
27
27
  end
@@ -2,6 +2,8 @@ module ActiveFedora
2
2
  module Datastreams
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ autoload :NokogiriDatastreams, 'active_fedora/datastreams/nokogiri_datastreams'
6
+
5
7
  included do
6
8
  class_attribute :ds_specs
7
9
  self.ds_specs = {'RELS-EXT'=> {:type=> ActiveFedora::RelsExtDatastream, :label=>"Fedora Object-to-Object Relationship Metadata", :block=>nil}}
@@ -62,7 +64,7 @@ module ActiveFedora
62
64
  end
63
65
 
64
66
  def datastream_from_spec(ds_spec, name)
65
- inner_object.datastream_object_for name, ds_spec
67
+ inner_object.datastream_object_for name, {}, ds_spec
66
68
  end
67
69
 
68
70
  def load_datastreams
@@ -197,23 +199,16 @@ module ActiveFedora
197
199
  # @option args [Boolean] :versionable Should versioned datastreams be stored
198
200
  # @yield block executed by some kinds of datastreams
199
201
  def has_metadata(*args, &block)
200
-
201
- if args.first.is_a? String
202
- name = args.first
203
- args = args[1] || {}
204
- args[:name] = name
205
- else
206
- args = args.first
207
- end
208
-
209
-
210
- spec = {:autocreate => args.fetch(:autocreate, false), :type => args[:type], :label => args.fetch(:label,""), :control_group => args[:control_group], :disseminator => args.fetch(:disseminator,""), :url => args.fetch(:url,""),:block => block}
211
- name = args[:name]
212
- raise ArgumentError, "You must provide a name (dsid) for the metadata datastream" unless name
213
- raise ArgumentError, "You must provide a :type property for the metadata datastream '#{name}'" unless spec[:type]
214
- spec[:versionable] = args[:versionable] if args.has_key? :versionable
215
- build_datastream_accessor(name)
216
- ds_specs[name]= spec
202
+ @metadata_ds_defaults ||= {
203
+ :autocreate => false,
204
+ :type=>nil,
205
+ :label=>"",
206
+ :control_group=>nil,
207
+ :disseminator=>"",
208
+ :url=>"",
209
+ :name=>nil
210
+ }
211
+ spec_datastream(args, @metadata_ds_defaults, &block)
217
212
  end
218
213
 
219
214
 
@@ -236,19 +231,14 @@ module ActiveFedora
236
231
  # @option args [Boolean] :autocreate Always create this datastream on new objects
237
232
  # @option args [Boolean] :versionable Should versioned datastreams be stored
238
233
  def has_file_datastream(*args)
239
- if args.first.is_a? String
240
- name = args.first
241
- args = args[1] || {}
242
- args[:name] = name
243
- else
244
- args = args.first || {}
245
- end
246
- spec = {:autocreate => args.fetch(:autocreate, false), :type => args.fetch(:type,ActiveFedora::Datastream),
247
- :label => args.fetch(:label,"File Datastream"), :control_group => args.fetch(:control_group,"M")}
248
- spec[:versionable] = args[:versionable] if args.has_key? :versionable
249
- name = args.fetch(:name, "content")
250
- build_datastream_accessor(name)
251
- ds_specs[name]= spec
234
+ @file_ds_defaults ||= {
235
+ :autocreate => false,
236
+ :type=>ActiveFedora::Datastream,
237
+ :label=>"File Datastream",
238
+ :control_group=>"M",
239
+ :name=>"content"
240
+ }
241
+ spec_datastream(args, @file_ds_defaults)
252
242
  end
253
243
 
254
244
  def build_datastream_accessor(dsid)
@@ -261,6 +251,28 @@ module ActiveFedora
261
251
 
262
252
  private
263
253
 
254
+ # Creates a datastream spec combining the given args and default values
255
+ # @param args [Array] either [String, Hash] or [Hash]; the latter must .has_key? :name
256
+ # @param defaults [Hash] the default values for the datastream spec
257
+ # @yield block executed by some kinds of datastreams
258
+ def spec_datastream(args, defaults, &block)
259
+ if args.first.is_a? String
260
+ name = args.first
261
+ args = args[1] || {}
262
+ args[:name] = name
263
+ else
264
+ args = args.first || {}
265
+ end
266
+ spec = defaults.merge(args.select {|key, value| defaults.has_key? key})
267
+ name = spec.delete(:name)
268
+ raise ArgumentError, "You must provide a name (dsid) for the datastream" unless name
269
+ raise ArgumentError, "You must provide a :type property for the datastream '#{name}'" unless spec[:type]
270
+ spec[:versionable] = args[:versionable] if args.has_key? :versionable
271
+ spec[:block] = block if block
272
+ build_datastream_accessor(name)
273
+ ds_specs[name]= spec
274
+ end
275
+
264
276
  ## Given a dsid return a standard name
265
277
  def name_for_dsid(dsid)
266
278
  dsid.gsub('-', '_')
@@ -0,0 +1,146 @@
1
+ module ActiveFedora
2
+ module Datastreams
3
+ module NokogiriDatastreams
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ # Create an instance of this class based on xml content
9
+ # @param [String, File, Nokogiri::XML::Node] xml the xml content to build from
10
+ # @param [ActiveFedora::OmDatastream] tmpl the Datastream object that you are building @default a new instance of this class
11
+ # Careful! If you call this from a constructor, be sure to provide something 'ie. self' as the @tmpl. Otherwise, you will get an infinite loop!
12
+ def from_xml(xml, tmpl=nil)
13
+ tmpl = self.new if tmpl.nil? ## This path is used only for unit testing (e.g. MarpaDCDatastream.from_xml(fixture("data.xml")) )
14
+
15
+ if !xml.present?
16
+ tmpl.ng_xml = self.xml_template
17
+ elsif xml.kind_of? Nokogiri::XML::Node || xml.kind_of?(Nokogiri::XML::Document)
18
+ tmpl.ng_xml = xml
19
+ else
20
+ tmpl.ng_xml = Nokogiri::XML::Document.parse(xml)
21
+ end
22
+
23
+ tmpl.ng_xml_doesnt_change!
24
+
25
+ return tmpl
26
+ end
27
+
28
+ def xml_template
29
+ Nokogiri::XML::Document.parse("<xml/>")
30
+ end
31
+
32
+ def decorate_ng_xml(xml)
33
+ xml
34
+ end
35
+ end
36
+
37
+
38
+ def ng_xml
39
+ @ng_xml ||= begin
40
+ if new?
41
+ ## Load up the template
42
+ xml = self.class.xml_template
43
+ else
44
+ xml = Nokogiri::XML::Document.parse(datastream_content)
45
+ end
46
+ self.class.decorate_ng_xml xml
47
+ end
48
+ end
49
+
50
+ def ng_xml=(new_xml)
51
+ # before we set ng_xml, we load the datastream so we know if the new value differs.
52
+ local_or_remote_content(true)
53
+
54
+ case new_xml
55
+ when Nokogiri::XML::Document
56
+ self.content=new_xml.to_xml
57
+ when Nokogiri::XML::Node
58
+ ## Cast a fragment to a document
59
+ self.content=new_xml.to_s
60
+ when String
61
+ self.content=new_xml
62
+ else
63
+ raise TypeError, "You passed a #{new_xml.class} into the ng_xml of the #{self.dsid} datastream. OmDatastream.ng_xml= only accepts Nokogiri::XML::Document, Nokogiri::XML::Element, Nokogiri::XML::Node, or raw XML (String) as inputs."
64
+ end
65
+ end
66
+
67
+ # don't want content eagerly loaded by proxy, so implementing methods that would be implemented by define_attribute_methods
68
+ def ng_xml_will_change!
69
+ changed_attributes['ng_xml'] = nil
70
+ end
71
+
72
+ def ng_xml_doesnt_change!
73
+ changed_attributes.delete('ng_xml')
74
+ end
75
+
76
+ # don't want content eagerly loaded by proxy, so implementing methods that would be implemented by define_attribute_methods
77
+ def ng_xml_changed?
78
+ changed_attributes.has_key? 'ng_xml'
79
+ end
80
+
81
+ def datastream_content
82
+ @datastream_content ||= Nokogiri::XML(super).to_xml {|config| config.no_declaration}.strip
83
+ end
84
+
85
+ def content=(new_content)
86
+ if inline?
87
+ # inline datastreams may be transformed by fedora 3, so we test for equivalence instead of equality
88
+ if !EquivalentXml.equivalent?(datastream_content, new_content)
89
+ ng_xml_will_change!
90
+ @ng_xml = Nokogiri::XML::Document.parse(new_content)
91
+ super(@ng_xml.to_s)
92
+ end
93
+ else
94
+ if datastream_content != new_content
95
+ ng_xml_will_change!
96
+ @ng_xml = Nokogiri::XML::Document.parse(new_content)
97
+ super(@ng_xml.to_s)
98
+ end
99
+ end
100
+ self.class.decorate_ng_xml @ng_xml
101
+ end
102
+
103
+ def content_changed?
104
+ return false if !xml_loaded
105
+ super
106
+ end
107
+
108
+ def to_xml(xml = nil)
109
+ xml = self.ng_xml if xml.nil?
110
+ ng_xml = self.ng_xml
111
+ if ng_xml.respond_to?(:root) && ng_xml.root.nil? && self.class.respond_to?(:root_property_ref) && !self.class.root_property_ref.nil?
112
+ ng_xml = self.class.generate(self.class.root_property_ref, "")
113
+ if xml.root.nil?
114
+ xml = ng_xml
115
+ end
116
+ end
117
+
118
+ unless xml == ng_xml || ng_xml.root.nil?
119
+ if xml.kind_of?(Nokogiri::XML::Document)
120
+ xml.root.add_child(ng_xml.root)
121
+ elsif xml.kind_of?(Nokogiri::XML::Node)
122
+ xml.add_child(ng_xml.root)
123
+ else
124
+ raise "You can only pass instances of Nokogiri::XML::Node into this method. You passed in #{xml}"
125
+ end
126
+ end
127
+
128
+ return xml.to_xml {|config| config.no_declaration}.strip
129
+ end
130
+
131
+ def local_or_remote_content(ensure_fetch = true)
132
+ @content = to_xml if ng_xml_changed? || autocreate?
133
+ super
134
+ end
135
+
136
+ def autocreate?
137
+ changed_attributes.has_key? :profile
138
+ end
139
+
140
+ def xml_loaded
141
+ instance_variable_defined? :@ng_xml
142
+ end
143
+
144
+ end
145
+ end
146
+ end