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.
- data/.rvmrc +1 -1
- data/Gemfile.lock +16 -10
- data/History.txt +4 -5
- data/README.textile +1 -1
- data/active-fedora.gemspec +2 -2
- data/lib/active_fedora.rb +36 -19
- data/lib/active_fedora/associations.rb +157 -0
- data/lib/active_fedora/associations/association_collection.rb +180 -0
- data/lib/active_fedora/associations/association_proxy.rb +177 -0
- data/lib/active_fedora/associations/belongs_to_association.rb +36 -0
- data/lib/active_fedora/associations/has_many_association.rb +52 -0
- data/lib/active_fedora/attribute_methods.rb +8 -0
- data/lib/active_fedora/base.rb +76 -80
- data/lib/active_fedora/datastream.rb +0 -1
- data/lib/active_fedora/delegating.rb +53 -0
- data/lib/active_fedora/model.rb +4 -2
- data/lib/active_fedora/nested_attributes.rb +153 -0
- data/lib/active_fedora/nokogiri_datastream.rb +17 -18
- data/lib/active_fedora/reflection.rb +140 -0
- data/lib/active_fedora/relationships_helper.rb +10 -5
- data/lib/active_fedora/semantic_node.rb +146 -57
- data/lib/active_fedora/solr_service.rb +0 -7
- data/lib/active_fedora/version.rb +1 -1
- data/lib/fedora/connection.rb +75 -111
- data/lib/fedora/repository.rb +14 -28
- data/lib/ruby-fedora.rb +1 -1
- data/spec/integration/associations_spec.rb +139 -0
- data/spec/integration/nested_attribute_spec.rb +40 -0
- data/spec/integration/repository_spec.rb +9 -14
- data/spec/integration/semantic_node_spec.rb +2 -0
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/active_fedora_spec.rb +2 -1
- data/spec/unit/association_proxy_spec.rb +13 -0
- data/spec/unit/base_active_model_spec.rb +61 -0
- data/spec/unit/base_delegate_spec.rb +59 -0
- data/spec/unit/base_spec.rb +45 -58
- data/spec/unit/connection_spec.rb +21 -21
- data/spec/unit/datastream_spec.rb +0 -11
- data/spec/unit/has_many_collection_spec.rb +27 -0
- data/spec/unit/model_spec.rb +1 -1
- data/spec/unit/nokogiri_datastream_spec.rb +3 -29
- data/spec/unit/repository_spec.rb +2 -2
- data/spec/unit/semantic_node_spec.rb +2 -0
- data/spec/unit/solr_service_spec.rb +0 -7
- metadata +36 -15
- data/lib/active_fedora/active_fedora_configuration_exception.rb +0 -2
- data/lib/util/class_level_inheritable_attributes.rb +0 -23
data/.rvmrc
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active-fedora (
|
5
|
-
activeresource (
|
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.
|
22
|
-
|
23
|
-
activesupport (=
|
24
|
-
|
25
|
-
|
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.
|
72
|
+
solrizer-fedora (1.1.1)
|
67
73
|
active-fedora (>= 2.3.0)
|
68
74
|
fastercsv
|
69
75
|
rsolr
|
data/History.txt
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
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
|
|
data/README.textile
CHANGED
@@ -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.
|
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
|
|
data/active-fedora.gemspec
CHANGED
@@ -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",
|
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")
|
data/lib/active_fedora.rb
CHANGED
@@ -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/
|
14
|
-
require 'active_fedora/
|
15
|
-
|
16
|
-
require 'active_fedora/
|
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
|