Module: OM::XML::TermValueOperators

Defined in:
lib/om/xml/term_value_operators.rb

Instance Method Summary collapse

Instance Method Details

#assign_nested_attributes_for_collection_association(pointer, new_values) ⇒ Object

Will update the name of the Person with ID 1, build a new associated person with the name ‘John’, and mark the associated Person with ID 2 for destruction.

Also accepts an Array of attribute hashes:

assign_nested_attributes_for_collection_association(:people, [
  { name: 'Peter' },
  { name: 'John', role: 'Creator' },
  { name: 'Bess' }
])

Examples:


assign_nested_attributes_for_collection_association(:people, {
  '1' => { name: 'Peter' },
  '2' => { name: 'John', role: 'Creator' },
  '3' => { name: 'Bess' }
})

See Also:

  • ActiveRecord::NestedAttributes#assign_nested_attributes_for_collection_association


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
# File 'lib/om/xml/term_value_operators.rb', line 72

def assign_nested_attributes_for_collection_association(pointer, new_values)
    
    term = self.class.terminology.retrieve_term( *OM.pointers_to_flat_array(pointer,false) )
    xpath = self.class.terminology.xpath_with_indexes(*pointer)
    current_values = term_values(*pointer)

    new_values = term.sanitize_new_values(new_values)
    

    if current_values.length > new_values.length
      starting_index = new_values.length + 1
      starting_index.upto(current_values.size).each do |index|
        term_value_delete select: xpath, child_index: index
      end
    end

    # Populate the response hash appropriately, using hierarchical names for terms as keys rather than the given pointers.
    result = new_values.dup
    
    # Fill out the pointer completely if the final term is a NamedTermProxy
    if term.kind_of? OM::XML::NamedTermProxy
      pointer.pop
      pointer = pointer.concat(term.proxy_pointer)
    end
    
    parent_pointer = pointer.dup
    parent_pointer.pop
    parent_xpath = self.class.terminology.xpath_with_indexes(*parent_pointer)

    template_pointer = OM.pointers_to_flat_array(pointer,false)
    
    # If the value doesn't exist yet, append it.  Otherwise, update the existing value.
    new_values.each_with_index do |z, y|
      if find_by_terms(*pointer)[y.to_i].nil?
        result.delete(y)
        term_values_append(:parent_select=>parent_pointer,:parent_index=>0,:template=>template_pointer,:values=>z)
        new_array_index = find_by_terms(*pointer).length - 1
        result[new_array_index] = z
      else
        term_value_update(xpath, y.to_i, z)
      end
    end

    return result
end

#build_ancestors(parent_select, parent_index) ⇒ Nokogiri::XML::Node, Array

Creates necesary ancestor nodes to support inserting a new term value where the ancestor node(s) don’t exist yet. Corrects node indexes in the pointer array to correspond to the ancestors that it creates. Returns a two-value array with the ‘parent’ node and a corrected pointer array

Returns:

  • (Nokogiri::XML::Node)

    the ‘parent’ (the final node in the ancestor tree)

  • (Array)

    corrected pointer array for retrieving this parent



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/om/xml/term_value_operators.rb', line 181

def build_ancestors(parent_select, parent_index)
  parent_select = Array(parent_select)
  parent_nodeset = find_by_terms(*parent_select)
  starting_point = node_from_set(parent_nodeset, parent_index)
  if starting_point.nil? 
    starting_point = [] 
  end
  to_build = []
  until !starting_point.empty?
    to_build = [parent_select.pop] + to_build
    starting_point = find_by_terms(*parent_select)
    if starting_point.empty? && parent_select.empty? 
      raise OM::XML::TemplateMissingException, "Cannot insert nodes into the document because it is empty.  Try defining self.xml_template on the #{self.class} class."
    end
  end
  to_build.each do |term_pointer|      
    parent_select << term_pointer
    
    # If pointers in parent_select don't match with the indexes of built ancestors, correct the hash
    if find_by_terms(*parent_select+[{}]).length == 0
      if parent_select.last.kind_of?(Hash)
        suspect_pointer = parent_select.pop
        term_key = suspect_pointer.keys.first
        if parent_select.empty?
          corrected_term_index = find_by_terms(term_key).length
        else
          corrected_term_index = find_by_terms(*parent_select+[{}]).length
        end
        parent_select << {term_key => corrected_term_index}
      end
    end
    template_pointer = OM.pointers_to_flat_array(parent_select,false)
    new_values = [""]
    insert_from_template(starting_point.first, new_values, template_pointer)
    starting_point = find_by_terms(*parent_select+[{}])
    # If pointers in parent_select don't match with the indexes of built ancestors, correct the hash
    if starting_point.empty?
      raise ::StandardError, "Oops.  Something went wrong adding #{term_pointer.inspect} to #{parent_select.inspect} while building ancestors.  Expected to find something at #{self.class.terminology.xpath_for(*parent_select)}. The current xml is\n #{self.to_xml}"
    end
  end
  if parent_index > starting_point.length
    parent_index = starting_point.length - 1
  end
  return node_from_set(starting_point, parent_index)
end

#delete_on_update?(node, new_value) ⇒ Boolean

Returns:

  • (Boolean)


238
239
240
# File 'lib/om/xml/term_value_operators.rb', line 238

def delete_on_update?(node, new_value)
  new_value.nil? || new_value == :delete
end

#insert_from_template(parent_node, new_values, template) ⇒ Nokogiri::XML::Node

Insert xml containing new_values into parent_node. Generate the xml based on template

Parameters:

  • parent_node (Nokogiri::XML::Node)

    to insert new xml into

  • new_values (Array)

    to build the xml around

  • template (Array -- (OM term pointer array) OR String -- (like what you would pass into Nokogiri::XML::Builder.new))

    for building the new xml. Use the syntax that Nokogiri::XML::Builder uses.

Returns:

  • (Nokogiri::XML::Node)

    the parent_node with new chldren inserted into it



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/om/xml/term_value_operators.rb', line 146

def insert_from_template(parent_node, new_values, template)
  ng_xml_will_change!
  # If template is a string, use it as the template, otherwise use it as arguments to xml_builder_template
  unless template.instance_of?(String)
    template_args = Array(template)
    if template_args.last.kind_of?(Hash)
      template_opts = template_args.delete_at(template_args.length - 1)
      template_args << template_opts
    end
    template_args = OM.pointers_to_flat_array(template_args,false)
    template = self.class.terminology.xml_builder_template( *template_args )
  end

  #if there is an xpath element pointing to text() need to change to just 'text' so it references the text method for the parent node
  template.gsub!(/text\(\)/, 'text')
  
  builder = Nokogiri::XML::Builder.with(parent_node) do |xml|
    new_values.each do |builder_new_value|
      builder_new_value = builder_new_value.gsub(/'/, "\\\\'") # escape any apostrophes in the new value
      if matchdata = /xml\.@(\w+)/.match(template)
        parent_node.set_attribute(matchdata[1], builder_new_value)
      else 
        builder_arg = eval('"'+ template + '"') # this inserts builder_new_value into the builder template
        eval(builder_arg)
      end
    end
  end
  return parent_node
end

#property_values(*lookup_args) ⇒ Object

alias for term_values



24
25
26
# File 'lib/om/xml/term_value_operators.rb', line 24

def property_values(*lookup_args)
  term_values(*lookup_args)
end

#term_value_delete(opts = {}) ⇒ Object

def term_value_set(term_ref, query_opts, node_index, new_value) end



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/om/xml/term_value_operators.rb', line 245

def term_value_delete(opts={})
  ng_xml_will_change!
  parent_select = Array( opts[:parent_select] )
  parent_index = opts[:parent_index]
  child_index = opts[:child_index]
  xpath_select = opts[:select]
  
  if !xpath_select.nil?
    node = find_by_terms_and_value(xpath_select).first
  else
    # parent_nodeset = find_by_terms_and_value(parent_select, parent_select)
    parent_nodeset = find_by_terms_and_value(*parent_select)
    
    if parent_index.nil?
      node = node_from_set(parent_nodeset, child_index)
    else
      parent = node_from_set(parent_nodeset, parent_index)
      # this next line is a hack around the fact that element_children() sometimes doesn't work.
      node = node_from_set(parent.xpath("*"), child_index)
    end
  end
  
  node.remove
end

#term_value_update(node_select, node_index, new_value, opts = {}) ⇒ Object



227
228
229
230
231
232
233
234
235
236
# File 'lib/om/xml/term_value_operators.rb', line 227

def term_value_update(node_select,node_index,new_value,opts={})
  ng_xml_will_change!
  
  node = find_by_terms_and_value(*node_select)[node_index]
  if delete_on_update?(node, new_value)
    node.remove
  else
    node.content = new_value
  end
end

#term_values(*term_pointer) ⇒ Object

Retrieves all of the nodes from the current document that match term_pointer and returns an array of their values



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/om/xml/term_value_operators.rb', line 7

def term_values(*term_pointer)
  result = []
  xpath = self.class.terminology.xpath_with_indexes(*term_pointer)
  #if value is on line by itself sometimes does not trim leading and trailing whitespace for a text node so will detect and fix it
  trim_text = !xpath.nil? && !xpath.index("text()").nil?
  find_by_terms(*term_pointer).each {|node| result << (trim_text ? node.text.strip : node.text) }

  if term_pointer.length == 1 && term_pointer.first.kind_of?(String)
    OM.logger.warn "Passing a xpath to term_values means that OM can not properly find the associated term. Pass a term pointer instead." if OM.logger
    result
  else
    term = self.class.terminology.retrieve_term(*OM.pointers_to_flat_array(OM.destringify(term_pointer), false))
    term.deserialize(result)
  end
end

#term_values_append(opts = {}) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/om/xml/term_value_operators.rb', line 118

def term_values_append(opts={})
  parent_select = Array( opts[:parent_select] )
  parent_index = opts[:parent_index]
  template = opts[:template]
  new_values = Array( opts[:values] )  
  
  parent_nodeset = find_by_terms(*parent_select)
  parent_node = node_from_set(parent_nodeset, parent_index)
  
  if parent_node.nil?
    if parent_select.empty?
      parent_node = ng_xml.root
    else
      parent_node = build_ancestors(parent_select, parent_index)
    end
  end

  insert_from_template(parent_node, new_values, template)
  
  return parent_node
  
end

#update_values(params = {}) ⇒ Object

Examples:

{[{":person"=>"0"}, "role", "text"]=>{'1'=>"role1", '2' => "role2", '3'=>"role3"}, [{:person=>1}, :family_name]=>"Andronicus", [{"person"=>"1"},:given_name]=>["Titus"],[{:person=>1},:role,:text]=>["otherrole1","otherrole2"] }

Parameters:

  • params (Hash) (defaults to: {})


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/om/xml/term_value_operators.rb', line 32

def update_values(params={})
  # remove any terms from params that this datastream doesn't recognize    
  
  params.delete_if do |term_pointer,new_values| 
    if term_pointer.kind_of?(String)
      true
    else
      !self.class.terminology.has_term?(*OM.destringify(term_pointer))
    end
  end
  
  params.inject({}) do |result, (term_pointer,new_values)|
    pointer = OM.destringify(term_pointer)
    hn = OM::XML::Terminology.term_hierarchical_name(*pointer)
    result[hn] = assign_nested_attributes_for_collection_association(pointer, new_values)
    result
  end
end