active-fedora 2.3.8 → 3.0.0

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 (47) hide show
  1. data/.rvmrc +1 -1
  2. data/Gemfile.lock +16 -10
  3. data/History.txt +4 -5
  4. data/README.textile +1 -1
  5. data/active-fedora.gemspec +2 -2
  6. data/lib/active_fedora.rb +36 -19
  7. data/lib/active_fedora/associations.rb +157 -0
  8. data/lib/active_fedora/associations/association_collection.rb +180 -0
  9. data/lib/active_fedora/associations/association_proxy.rb +177 -0
  10. data/lib/active_fedora/associations/belongs_to_association.rb +36 -0
  11. data/lib/active_fedora/associations/has_many_association.rb +52 -0
  12. data/lib/active_fedora/attribute_methods.rb +8 -0
  13. data/lib/active_fedora/base.rb +76 -80
  14. data/lib/active_fedora/datastream.rb +0 -1
  15. data/lib/active_fedora/delegating.rb +53 -0
  16. data/lib/active_fedora/model.rb +4 -2
  17. data/lib/active_fedora/nested_attributes.rb +153 -0
  18. data/lib/active_fedora/nokogiri_datastream.rb +17 -18
  19. data/lib/active_fedora/reflection.rb +140 -0
  20. data/lib/active_fedora/relationships_helper.rb +10 -5
  21. data/lib/active_fedora/semantic_node.rb +146 -57
  22. data/lib/active_fedora/solr_service.rb +0 -7
  23. data/lib/active_fedora/version.rb +1 -1
  24. data/lib/fedora/connection.rb +75 -111
  25. data/lib/fedora/repository.rb +14 -28
  26. data/lib/ruby-fedora.rb +1 -1
  27. data/spec/integration/associations_spec.rb +139 -0
  28. data/spec/integration/nested_attribute_spec.rb +40 -0
  29. data/spec/integration/repository_spec.rb +9 -14
  30. data/spec/integration/semantic_node_spec.rb +2 -0
  31. data/spec/spec_helper.rb +1 -3
  32. data/spec/unit/active_fedora_spec.rb +2 -1
  33. data/spec/unit/association_proxy_spec.rb +13 -0
  34. data/spec/unit/base_active_model_spec.rb +61 -0
  35. data/spec/unit/base_delegate_spec.rb +59 -0
  36. data/spec/unit/base_spec.rb +45 -58
  37. data/spec/unit/connection_spec.rb +21 -21
  38. data/spec/unit/datastream_spec.rb +0 -11
  39. data/spec/unit/has_many_collection_spec.rb +27 -0
  40. data/spec/unit/model_spec.rb +1 -1
  41. data/spec/unit/nokogiri_datastream_spec.rb +3 -29
  42. data/spec/unit/repository_spec.rb +2 -2
  43. data/spec/unit/semantic_node_spec.rb +2 -0
  44. data/spec/unit/solr_service_spec.rb +0 -7
  45. metadata +36 -15
  46. data/lib/active_fedora/active_fedora_configuration_exception.rb +0 -2
  47. data/lib/util/class_level_inheritable_attributes.rb +0 -23
data/.rvmrc CHANGED
@@ -4,7 +4,7 @@
4
4
  # development environment upon cd'ing into the directory
5
5
 
6
6
  ruby_string="ree-1.8.7"
7
- gemset_name="active_fedora_2.x"
7
+ gemset_name="active_fedora"
8
8
 
9
9
  # Install rubies when used instead of only displaying a warning and exiting
10
10
  rvm_install_on_use_flag=1
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active-fedora (2.3.7)
5
- activeresource (< 3.0.0)
4
+ active-fedora (3.0.0)
5
+ activeresource (~> 3.0.0)
6
+ activesupport (~> 3.0.0)
6
7
  equivalent-xml
7
8
  facets
8
9
  mediashelf-loggable
9
10
  mime-types (>= 1.16)
10
11
  multipart-post (= 1.1.2)
11
- net-http-persistent
12
12
  nokogiri
13
13
  om (>= 1.0)
14
14
  solr-ruby (>= 0.0.6)
@@ -18,17 +18,23 @@ PATH
18
18
  GEM
19
19
  remote: http://rubygems.org/
20
20
  specs:
21
- RedCloth (4.2.8)
22
- activeresource (2.3.14)
23
- activesupport (= 2.3.14)
24
- activesupport (2.3.14)
25
- builder (3.0.0)
21
+ RedCloth (4.2.7)
22
+ activemodel (3.0.10)
23
+ activesupport (= 3.0.10)
24
+ builder (~> 2.1.2)
25
+ i18n (~> 0.5.0)
26
+ activeresource (3.0.10)
27
+ activemodel (= 3.0.10)
28
+ activesupport (= 3.0.10)
29
+ activesupport (3.0.10)
30
+ builder (2.1.2)
26
31
  columnize (0.3.4)
27
32
  daemons (1.1.4)
28
33
  equivalent-xml (0.2.7)
29
34
  nokogiri (>= 1.4.3)
30
35
  facets (2.9.2)
31
36
  fastercsv (1.5.4)
37
+ i18n (0.5.0)
32
38
  jettywrapper (0.0.10)
33
39
  logger
34
40
  mediashelf-loggable
@@ -38,8 +44,8 @@ GEM
38
44
  mediashelf-loggable (0.4.7)
39
45
  mime-types (1.16)
40
46
  mocha (0.9.12)
47
+ multi_json (1.0.3)
41
48
  multipart-post (1.1.2)
42
- net-http-persistent (2.1)
43
49
  nokogiri (1.5.0)
44
50
  om (1.4.0)
45
51
  mediashelf-loggable
@@ -63,7 +69,7 @@ GEM
63
69
  om (>= 1.0.0)
64
70
  stomp
65
71
  xml-simple
66
- solrizer-fedora (1.1.0)
72
+ solrizer-fedora (1.1.1)
67
73
  active-fedora (>= 2.3.0)
68
74
  fastercsv
69
75
  rsolr
@@ -1,9 +1,8 @@
1
- 2.3.8
2
-
3
- HYDRA-670 -- Use persistent http connections
4
- Don't set log level to error by default
5
- Benchmark fedora and solr queries
1
+ 3.0.0
6
2
 
3
+ added belongs_to and has_many model relationships
4
+ added nested_attributes_for
5
+ Added dependency on rails > 3.0.0
7
6
 
8
7
  2.3.7
9
8
 
@@ -1,6 +1,6 @@
1
1
  h2. Description
2
2
 
3
- RubyFedora and ActiveFedora provide a set of Ruby gems for creating and managing objects in the Fedora Repository Architecture ("http://fedora-commons.org":http://fedora-commons.org). ActiveFedora is loosely based on "ActiveRecord" in Rails. This version of ActiveFedora depends on Rails 2.3.11 and has been superceeded by ActiveFedora 3.x which requires Rails 3.
3
+ RubyFedora and ActiveFedora provide a set of Ruby gems for creating and managing objects in the Fedora Repository Architecture ("http://fedora-commons.org":http://fedora-commons.org). ActiveFedora is loosely based on "ActiveRecord" in Rails. The 3.x series of ActiveFedora depends on Rails 3, specifically activemodel and activesupport.
4
4
 
5
5
  h2. Getting Help
6
6
 
@@ -22,11 +22,11 @@ Gem::Specification.new do |s|
22
22
  s.add_dependency('nokogiri')
23
23
  s.add_dependency('om', '>= 1.0')
24
24
  s.add_dependency('solrizer', '>1.0.0')
25
- s.add_dependency("activeresource", "<3.0.0")
25
+ s.add_dependency("activeresource", '~> 3.0.0')
26
+ s.add_dependency("activesupport", '~> 3.0.0')
26
27
  s.add_dependency("mediashelf-loggable")
27
28
  s.add_dependency("equivalent-xml")
28
29
  s.add_dependency("facets")
29
- s.add_dependency("net-http-persistent")
30
30
  s.add_development_dependency("yard")
31
31
  s.add_development_dependency("RedCloth") # for RDoc formatting
32
32
  s.add_development_dependency("rake")
@@ -1,36 +1,49 @@
1
1
  require 'rubygems'
2
- # require "bundler/setup"
3
- # Bundler.require(:default)
4
-
5
2
  gem 'solr-ruby'
6
3
  require "loggable"
7
4
 
8
5
  $: << 'lib'
6
+
7
+ require 'active_support'
8
+ require 'active_model'
9
+
9
10
  require 'active_fedora/solr_service.rb'
10
11
  require "solrizer"
11
12
 
12
13
  require 'ruby-fedora'
13
- require 'active_fedora/base.rb'
14
- require 'active_fedora/content_model.rb'
15
- require 'active_fedora/datastream.rb'
16
- require 'active_fedora/fedora_object.rb'
17
- require 'active_fedora/metadata_datastream_helper.rb'
18
- require 'active_fedora/metadata_datastream.rb'
19
- require 'active_fedora/nokogiri_datastream'
20
- require 'active_fedora/model.rb'
21
- require 'active_fedora/property.rb'
22
- require 'active_fedora/qualified_dublin_core_datastream.rb'
23
- require 'active_fedora/relationship.rb'
24
- require 'active_fedora/rels_ext_datastream.rb'
25
- require 'active_fedora/semantic_node.rb'
26
- require 'active_fedora/version.rb'
27
-
28
- require 'active_fedora/railtie' if defined?(Rails) && Rails.version >= "3.0"
14
+ # require 'active_fedora/fedora_object.rb'
15
+ # require 'active_fedora/version.rb'
16
+ #
17
+ # require 'active_fedora/railtie' if defined?(Rails) && Rails.version >= "3.0"
29
18
 
30
19
  SOLR_DOCUMENT_ID = ActiveFedora::SolrService.id_field unless defined?(SOLR_DOCUMENT_ID)
31
20
  ENABLE_SOLR_UPDATES = true unless defined?(ENABLE_SOLR_UPDATES)
32
21
 
33
22
  module ActiveFedora #:nodoc:
23
+ extend ActiveSupport::Autoload
24
+
25
+ eager_autoload do
26
+ autoload :Associations
27
+ autoload :AttributeMethods
28
+ autoload :Base
29
+ autoload :ContentModel
30
+ autoload :Reflection
31
+ autoload :Relationship
32
+ autoload :Datastream
33
+ autoload :Delegating
34
+ autoload :Model
35
+ autoload :MetadataDatastream
36
+ autoload :MetadataDatastreamHelper
37
+ autoload :NokogiriDatastream
38
+ autoload :Property
39
+ autoload :QualifiedDublinCoreDatastream
40
+ autoload :RelsExtDatastream
41
+ autoload :RelationshipsHelper
42
+ autoload :SemanticNode
43
+ autoload :NestedAttributes
44
+
45
+ end
46
+
34
47
 
35
48
  include Loggable
36
49
 
@@ -68,6 +81,7 @@ module ActiveFedora #:nodoc:
68
81
  # 2. If it does not find a solr.yml and the fedora.yml contains a solr url, it will raise an configuration error
69
82
  # 3. If it does not find a solr.yml and the fedora.yml does not contain a solr url, it will look in: +Rails.root+/config, +current working directory+/config, then the solr.yml shipped with gem
70
83
  def self.init( options={} )
84
+ logger.level = Logger::ERROR if logger.respond_to? :level ###MediaShelf StubLogger doesn't have a level= method
71
85
  # Make config_options into a Hash if nil is passed in as the value
72
86
  options = {} if options.nil?
73
87
 
@@ -276,5 +290,8 @@ module ActiveFedora
276
290
  class ServerError < Fedora::ServerError; end # :nodoc:
277
291
  class ObjectNotFoundError < RuntimeError; end # :nodoc:
278
292
  class PredicateMappingsNotFoundError < RuntimeError; end # :nodoc:
293
+ class UnknownAttributeError < NoMethodError; end; # :nodoc:
294
+ class ActiveFedoraConfigurationException < Exception; end # :nodoc:
295
+
279
296
  end
280
297
 
@@ -0,0 +1,157 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module ActiveFedora
5
+ module Associations
6
+ extend ActiveSupport::Concern
7
+
8
+ autoload :HasManyAssociation, 'active_fedora/associations/has_many_association'
9
+ autoload :BelongsToAssociation, 'active_fedora/associations/belongs_to_association'
10
+
11
+ autoload :AssociationCollection, 'active_fedora/associations/association_collection'
12
+ autoload :AssociationProxy, 'active_fedora/associations/association_proxy'
13
+ private
14
+
15
+ # Returns the specified association instance if it responds to :loaded?, nil otherwise.
16
+ def association_instance_get(name)
17
+ ivar = "@#{name}"
18
+ if instance_variable_defined?(ivar)
19
+ association = instance_variable_get(ivar)
20
+ association if association.respond_to?(:loaded?)
21
+ end
22
+ end
23
+
24
+ # Set the specified association instance.
25
+ def association_instance_set(name, association)
26
+ instance_variable_set("@#{name}", association)
27
+ end
28
+
29
+
30
+
31
+ module ClassMethods
32
+
33
+ def has_many(association_id, options={})
34
+ raise "You must specify a property name for #{name}" if !options[:property]
35
+ has_relationship association_id.to_s, options[:property], :inbound => true
36
+ reflection = create_has_many_reflection(association_id, options)
37
+ collection_accessor_methods(reflection, HasManyAssociation)
38
+ end
39
+
40
+
41
+ def belongs_to(association_id, options = {})
42
+ raise "You must specify a property name for #{name}" if !options[:property]
43
+ has_relationship association_id.to_s, options[:property]
44
+ reflection = create_belongs_to_reflection(association_id, options)
45
+
46
+ association_accessor_methods(reflection, BelongsToAssociation)
47
+ # association_constructor_method(:build, reflection, BelongsToAssociation)
48
+ # association_constructor_method(:create, reflection, BelongsToAssociation)
49
+ #configure_dependency_for_belongs_to(reflection)
50
+ end
51
+
52
+
53
+ private
54
+
55
+ def create_has_many_reflection(association_id, options)
56
+ create_reflection(:has_many, association_id, options, self)
57
+ end
58
+
59
+ def create_belongs_to_reflection(association_id, options)
60
+ create_reflection(:belongs_to, association_id, options, self)
61
+ end
62
+
63
+ def association_accessor_methods(reflection, association_proxy_class)
64
+ redefine_method(reflection.name) do |*params|
65
+ force_reload = params.first unless params.empty?
66
+ association = association_instance_get(reflection.name)
67
+
68
+ if association.nil? || force_reload
69
+ association = association_proxy_class.new(self, reflection)
70
+ retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
71
+ if retval.nil? and association_proxy_class == BelongsToAssociation
72
+ association_instance_set(reflection.name, nil)
73
+ return nil
74
+ end
75
+ association_instance_set(reflection.name, association)
76
+ end
77
+
78
+ association.target.nil? ? nil : association
79
+ end
80
+
81
+ redefine_method("loaded_#{reflection.name}?") do
82
+ association = association_instance_get(reflection.name)
83
+ association && association.loaded?
84
+ end
85
+
86
+ redefine_method("#{reflection.name}=") do |new_value|
87
+ association = association_instance_get(reflection.name)
88
+
89
+ if association.nil? || association.target != new_value
90
+ association = association_proxy_class.new(self, reflection)
91
+ end
92
+
93
+ association.replace(new_value)
94
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
95
+ end
96
+
97
+ redefine_method("set_#{reflection.name}_target") do |target|
98
+ return if target.nil? and association_proxy_class == BelongsToAssociation
99
+ association = association_proxy_class.new(self, reflection)
100
+ association.target = target
101
+ association_instance_set(reflection.name, association)
102
+ end
103
+
104
+ redefine_method("#{reflection.name}_id=") do |new_value|
105
+ send("#{reflection.name}=", reflection.klass.find(new_value))
106
+ end
107
+ redefine_method("#{reflection.name}_id") do
108
+ obj = send("#{reflection.name}")
109
+ obj.pid if obj
110
+ end
111
+ end
112
+
113
+
114
+ def collection_reader_method(reflection, association_proxy_class)
115
+ redefine_method(reflection.name) do |*params|
116
+
117
+ force_reload = params.first unless params.empty?
118
+ association = association_instance_get(reflection.name)
119
+ unless association
120
+ association = association_proxy_class.new(self, reflection)
121
+ association_instance_set(reflection.name, association)
122
+ end
123
+
124
+ association.reload if force_reload
125
+
126
+ association
127
+ end
128
+
129
+ redefine_method("#{reflection.name.to_s.singularize}_ids") do
130
+ send(reflection.name).map { |r| r.pid }
131
+ end
132
+
133
+ end
134
+
135
+
136
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
137
+ collection_reader_method(reflection, association_proxy_class)
138
+
139
+ if writer
140
+ redefine_method("#{reflection.name}=") do |new_value|
141
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
142
+ association = send(reflection.name)
143
+ association.replace(new_value)
144
+ association
145
+ end
146
+
147
+ redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
148
+ ids = (new_value || []).reject { |nid| nid.blank? }
149
+ #TODO, like this when find() can return multiple records
150
+ #send("#{reflection.name}=", reflection.klass.find(ids))
151
+ send("#{reflection.name}=", ids.collect { |id| reflection.klass.find(id)})
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,180 @@
1
+ module ActiveFedora
2
+ module Associations
3
+ class AssociationCollection < AssociationProxy #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ end
7
+
8
+ # Returns the size of the collection
9
+ #
10
+ # If the collection has been already loaded +size+ and +length+ are
11
+ # equivalent. If not and you are going to need the records anyway
12
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
13
+ #
14
+ # This method is abstract in the sense that it relies on
15
+ # +count_records+, which is a method descendants have to provide.
16
+ def size
17
+ if @owner.new_record? && @target
18
+ @target.size
19
+ elsif !loaded? && @target.is_a?(Array)
20
+ unsaved_records = @target.select { |r| r.new_record? }
21
+ unsaved_records.size + count_records
22
+ else
23
+ count_records
24
+ end
25
+ end
26
+
27
+ # Replace this collection with +other_array+
28
+ # This will perform a diff and delete/add only records that have changed.
29
+ def replace(other_array)
30
+ other_array.each { |val| raise_on_type_mismatch(val) }
31
+
32
+ load_target
33
+ other = other_array.size < 100 ? other_array : other_array.to_set
34
+ current = @target.size < 100 ? @target : @target.to_set
35
+
36
+ delete(@target.select { |v| !other.include?(v) })
37
+ concat(other_array.select { |v| !current.include?(v) })
38
+ end
39
+
40
+
41
+ def to_ary
42
+ load_target
43
+ if @target.is_a?(Array)
44
+ @target.to_ary
45
+ else
46
+ Array.wrap(@target)
47
+ end
48
+ end
49
+ alias_method :to_a, :to_ary
50
+
51
+ def reset
52
+ reset_target!
53
+ @loaded = false
54
+ end
55
+
56
+
57
+ def build(attributes = {}, &block)
58
+ if attributes.is_a?(Array)
59
+ attributes.collect { |attr| build(attr, &block) }
60
+ else
61
+ build_record(attributes) do |record|
62
+ block.call(record) if block_given?
63
+ set_belongs_to_association_for(record)
64
+ end
65
+ end
66
+ end
67
+
68
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
69
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
70
+ def <<(*records)
71
+ result = true
72
+ load_target if @owner.new_record?
73
+
74
+ flatten_deeper(records).each do |record|
75
+ raise_on_type_mismatch(record)
76
+ add_record_to_target_with_callbacks(record) do |r|
77
+ result &&= insert_record(record) unless @owner.new_record?
78
+ end
79
+ end
80
+
81
+ result && self
82
+ end
83
+
84
+ alias_method :push, :<<
85
+ alias_method :concat, :<<
86
+
87
+ # Removes +records+ from this association calling +before_remove+ and
88
+ # +after_remove+ callbacks.
89
+ #
90
+ # This method is abstract in the sense that +delete_records+ has to be
91
+ # provided by descendants. Note this method does not imply the records
92
+ # are actually removed from the database, that depends precisely on
93
+ # +delete_records+. They are in any case removed from the collection.
94
+ def delete(*records)
95
+ remove_records(records) do |_records, old_records|
96
+ delete_records(old_records) if old_records.any?
97
+ _records.each { |record| @target.delete(record) }
98
+ end
99
+ end
100
+
101
+
102
+ def load_target
103
+ if !@owner.new_record?
104
+ begin
105
+ if !loaded?
106
+ if @target.is_a?(Array) && @target.any?
107
+ @target = find_target.map do |f|
108
+ i = @target.index(f)
109
+ if i
110
+ @target.delete_at(i).tap do |t|
111
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
112
+ t.attributes = f.attributes.except(*keys)
113
+ end
114
+ else
115
+ f
116
+ end
117
+ end + @target
118
+ else
119
+ @target = find_target
120
+ end
121
+ end
122
+ rescue ObjectNotFoundError # TODO this isn't ever thrown. Maybe check for nil instead
123
+ reset
124
+ end
125
+ end
126
+
127
+ loaded if target
128
+ target
129
+ end
130
+
131
+ def find_target
132
+ @owner.load_inbound_relationship(@reflection.name.to_s, @reflection.options[:property])
133
+ end
134
+
135
+
136
+ def add_record_to_target_with_callbacks(record)
137
+ # callback(:before_add, record)
138
+ yield(record) if block_given?
139
+ @target ||= [] unless loaded?
140
+ if index = @target.index(record)
141
+ @target[index] = record
142
+ else
143
+ @target << record
144
+ end
145
+ # callback(:after_add, record)
146
+ # set_inverse_instance(record, @owner)
147
+ record
148
+ end
149
+
150
+ protected
151
+ def reset_target!
152
+ @target = Array.new
153
+ end
154
+
155
+
156
+ private
157
+
158
+ def build_record(attrs)
159
+ #attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
160
+ record = @reflection.build_association(attrs)
161
+ if block_given?
162
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
163
+ else
164
+ add_record_to_target_with_callbacks(record)
165
+ end
166
+ end
167
+
168
+ def remove_records(*records)
169
+ records = flatten_deeper(records)
170
+ records.each { |record| raise_on_type_mismatch(record) }
171
+
172
+ #records.each { |record| callback(:before_remove, record) }
173
+ old_records = records.reject { |r| r.new_record? }
174
+ yield(records, old_records)
175
+ #records.each { |record| callback(:after_remove, record) }
176
+ end
177
+
178
+ end
179
+ end
180
+ end