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.
- 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
|