22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# File 'lib/xml_active.rb', line 22
def one_from_xml(source_xml, options = [])
@data_active_options = options
current_node = root_node_in source_xml
if current_node.name.underscore.eql?(self.name.underscore)
pk_node = current_node.xpath self.primary_key.to_s
active_record = find_record_based_on(pk_node)
unless active_record.nil?
if options.include? :update or options.include? :sync or options.include? :create
assign_attributes_from current_node, :to => active_record
if options.include? :fail_on_invalid and !active_record.valid?
messages = active_record.errors.messages.map {|attribute, messages| "#{attribute} #{messages.map{|message| message }.join(', ')}"}.join(', ')
raise "Found an invalid #{active_record.class.name} with the following errors: #{messages}. Source: #{current_node.to_s}"
end
end
self.reflect_on_all_associations.each do |association|
foreign_key = foreign_key_from(association)
klass = association.klass
case
when association.macro == :has_many, association.macro == :has_and_belongs_to_many
instances = instances_for association, :from => current_node, :for => active_record
child_ids = []
instances.each do |instance|
new_record = klass.one_from_xml(instance, options)
if new_record != nil
child_ids << new_record[klass.primary_key]
active_record.__send__(klass.name.underscore.pluralize.to_sym) << new_record
end
end
unless active_record.new_record?
if options.include?(:sync) or options.include?(:destroy)
if child_ids.length > 0
klass.destroy_all [klass.primary_key.to_s + " not in (?) and #{foreign_key} = ?", child_ids.collect, active_record.attributes[self.primary_key.to_s]]
else
klass.destroy_all
end
end
end
when association.macro == :has_one
pk_value = active_record.new_record? ? 0 : active_record.attributes[self.primary_key.to_s]
single_objects = current_node.xpath("//#{self.name.underscore}[#{self.primary_key}=#{pk_value}]/#{association.name}")
klass = association.klass
record = klass.where(foreign_key => active_record.attributes[self.primary_key.to_s]).all
if single_objects.count == 1
if record.count == 1
db_pk_value = record[0][klass.primary_key]
xml_pk_value = Integer(single_objects[0].element_children.xpath("//#{self.name.underscore}/#{klass.primary_key}").text)
if db_pk_value != xml_pk_value
if options.include?(:sync) or options.include?(:destroy)
klass.destroy(record[0][klass.primary_key])
end
end
elsif record.count > 1
raise "Too many records for one to one association in the database. Found #{record.count} records of '#{association.name}' for association with '#{self.name}'"
end
if options.include?(:create) or options.include?(:update) or options.include?(:sync)
new_record = klass.one_from_xml(single_objects[0], options)
if new_record != nil
new_record[foreign_key.to_sym] = active_record[self.primary_key.to_s]
new_record.save!
end
end
elsif single_objects.count > 1
raise "Too many records for one to one association in the provided XML. Found #{single_objects.count} records of '#{association.name}' for association with '#{self.name}'"
else
if record.count > 0 and options.include?(:sync) or options.include?(:destroy)
klass.destroy_all("#{foreign_key} = #{active_record.attributes[self.primary_key.to_s]}")
end
end
when association.macro == :belongs_to
else
raise "unsupported association #{association.macro} for #{association.name } on #{self.name}"
end
end
if options.include? :sync
active_record.save
elsif options.include?(:create) and active_record.new_record?
active_record.save
elsif options.include?(:update) and not active_record.new_record?
active_record.save
end
end
active_record
else
raise "The supplied XML (#{current_node.name}) cannot be mapped to this class (#{self.name})"
end
end
|