active-fedora 2.3.8 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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