]> git.openstreetmap.org Git - rails.git/blobdiff - app/models/relation.rb
Shut up libxml's "Must specify a string with one or more characters" error on relatio...
[rails.git] / app / models / relation.rb
index 0159c60b875d791ccb7b690ea12a263d436f78d2..c3769b14d68e2a126d2f54002e746127bc61d80d 100644 (file)
@@ -27,14 +27,13 @@ class Relation < ActiveRecord::Base
 
   def self.from_xml(xml, create=false)
     begin
-      p = XML::Parser.new
-      p.string = xml
+      p = XML::Parser.string(xml)
       doc = p.parse
 
       doc.find('//osm/relation').each do |pt|
         return Relation.from_xml_node(pt, create)
       end
-    rescue LibXML::XML::Error => ex
+    rescue LibXML::XML::Error, ArgumentError => ex
       raise OSM::APIBadXMLError.new("relation", xml, ex.message)
     end
   end
@@ -49,6 +48,8 @@ class Relation < ActiveRecord::Base
     raise OSM::APIBadXMLError.new("relation", pt, "You are missing the required changeset in the relation") if pt['changeset'].nil?
     relation.changeset_id = pt['changeset']
 
+    # The follow block does not need to be executed because they are dealt with 
+    # in create_with_history, update_from and delete_with_history
     if create
       relation.timestamp = Time.now
       relation.visible = true
@@ -209,11 +210,123 @@ class Relation < ActiveRecord::Base
 
     # duplicate tags are now forbidden, so we can't allow values
     # in the hash to be overwritten.
-    raise OSM::APIDuplicateTagsError.new if @tags.include? k
+    raise OSM::APIDuplicateTagsError.new("relation", self.id, k) if @tags.include? k
 
     @tags[k] = v
   end
 
+  ##
+  # updates the changeset bounding box to contain the bounding box of 
+  # the element with given +type+ and +id+. this only works with nodes
+  # and ways at the moment, as they're the only elements to respond to
+  # the :bbox call.
+  def update_changeset_element(type, id)
+    element = Kernel.const_get(type.capitalize).find(id)
+    changeset.update_bbox! element.bbox
+  end    
+
+  def delete_with_history!(new_relation, user)
+    unless self.visible
+      raise OSM::APIAlreadyDeletedError.new
+    end
+
+    # need to start the transaction here, so that the database can 
+    # provide repeatable reads for the used-by checks. this means it
+    # shouldn't be possible to get race conditions.
+    Relation.transaction do
+      check_consistency(self, new_relation, user)
+      # This will check to see if this relation is used by another relation
+      if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = ? AND member_type='relation' and member_id=? ", true, self.id ])
+        raise OSM::APIPreconditionFailedError.new("The relation #{new_relation.id} is a used in another relation")
+      end
+      self.changeset_id = new_relation.changeset_id
+      self.tags = {}
+      self.members = []
+      self.visible = false
+      save_with_history!
+    end
+  end
+
+  def update_from(new_relation, user)
+    check_consistency(self, new_relation, user)
+    if !new_relation.preconditions_ok?
+      raise OSM::APIPreconditionFailedError.new
+    end
+    self.changeset_id = new_relation.changeset_id
+    self.tags = new_relation.tags
+    self.members = new_relation.members
+    self.visible = true
+    save_with_history!
+  end
+  
+  def create_with_history(user)
+    check_create_consistency(self, user)
+    if !self.preconditions_ok?
+      raise OSM::APIPreconditionFailedError.new
+    end
+    self.version = 0
+    self.visible = true
+    save_with_history!
+  end
+
+  def preconditions_ok?
+    # These are hastables that store an id in the index of all 
+    # the nodes/way/relations that have already been added.
+    # If the member is valid and visible then we add it to the 
+    # relevant hash table, with the value true as a cache.
+    # Thus if you have nodes with the ids of 50 and 1 already in the
+    # relation, then the hash table nodes would contain:
+    # => {50=>true, 1=>true}
+    elements = { :node => Hash.new, :way => Hash.new, :relation => Hash.new }
+    self.members.each do |m|
+      # find the hash for the element type or die
+      hash = elements[m[0].to_sym] or return false
+
+      # unless its in the cache already
+      unless hash.key? m[1]
+        # use reflection to look up the appropriate class
+        model = Kernel.const_get(m[0].capitalize)
+
+        # get the element with that ID
+        element = model.find(m[1])
+
+        # and check that it is OK to use.
+        unless element and element.visible? and element.preconditions_ok?
+          return false
+        end
+        hash[m[1]] = true
+      end
+    end
+
+    return true
+  rescue
+    return false
+  end
+
+  # Temporary method to match interface to nodes
+  def tags_as_hash
+    return self.tags
+  end
+
+  ##
+  # if any members are referenced by placeholder IDs (i.e: negative) then
+  # this calling this method will fix them using the map from placeholders 
+  # to IDs +id_map+. 
+  def fix_placeholders!(id_map)
+    self.members.map! do |type, id, role|
+      old_id = id.to_i
+      if old_id < 0
+        new_id = id_map[type.to_sym][old_id]
+        raise "invalid placeholder" if new_id.nil?
+        [type, new_id, role]
+      else
+        [type, id, role]
+      end
+    end
+  end
+
+  private
+  
   def save_with_history!
     Relation.transaction do
       # have to be a little bit clever here - to detect if any tags
@@ -336,109 +449,4 @@ class Relation < ActiveRecord::Base
     end
   end
 
-  ##
-  # updates the changeset bounding box to contain the bounding box of 
-  # the element with given +type+ and +id+. this only works with nodes
-  # and ways at the moment, as they're the only elements to respond to
-  # the :bbox call.
-  def update_changeset_element(type, id)
-    element = Kernel.const_get(type.capitalize).find(id)
-    changeset.update_bbox! element.bbox
-  end    
-
-  def delete_with_history!(new_relation, user)
-    if self.visible
-      check_consistency(self, new_relation, user)
-      # This will check to see if this relation is used by another relation
-      if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = ? AND member_type='relation' and member_id=? ", true, self.id ])
-        raise OSM::APIPreconditionFailedError.new("The relation #{new_relation.id} is a used in another relation")
-      end
-      self.changeset_id = new_relation.changeset_id
-      self.tags = {}
-      self.members = []
-      self.visible = false
-      save_with_history!
-    else
-      raise OSM::APIAlreadyDeletedError.new
-    end
-  end
-
-  def update_from(new_relation, user)
-    check_consistency(self, new_relation, user)
-    if !new_relation.preconditions_ok?
-      raise OSM::APIPreconditionFailedError.new
-    end
-    self.changeset_id = new_relation.changeset_id
-    self.tags = new_relation.tags
-    self.members = new_relation.members
-    self.visible = true
-    save_with_history!
-  end
-  
-  def create_with_history(user)
-    check_create_consistency(self, user)
-    if !self.preconditions_ok?
-      raise OSM::APIPreconditionFailedError.new
-    end
-    self.version = 0
-    self.visible = true
-    save_with_history!
-  end
-
-  def preconditions_ok?
-    # These are hastables that store an id in the index of all 
-    # the nodes/way/relations that have already been added.
-    # If the member is valid and visible then we add it to the 
-    # relevant hash table, with the value true as a cache.
-    # Thus if you have nodes with the ids of 50 and 1 already in the
-    # relation, then the hash table nodes would contain:
-    # => {50=>true, 1=>true}
-    elements = { :node => Hash.new, :way => Hash.new, :relation => Hash.new }
-    self.members.each do |m|
-      # find the hash for the element type or die
-      hash = elements[m[0].to_sym] or return false
-
-      # unless its in the cache already
-      unless hash.key? m[1]
-        # use reflection to look up the appropriate class
-        model = Kernel.const_get(m[0].capitalize)
-
-        # get the element with that ID
-        element = model.find(m[1])
-
-        # and check that it is OK to use.
-        unless element and element.visible? and element.preconditions_ok?
-          return false
-        end
-        hash[m[1]] = true
-      end
-    end
-
-    return true
-  rescue
-    return false
-  end
-
-  # Temporary method to match interface to nodes
-  def tags_as_hash
-    return self.tags
-  end
-
-  ##
-  # if any members are referenced by placeholder IDs (i.e: negative) then
-  # this calling this method will fix them using the map from placeholders 
-  # to IDs +id_map+. 
-  def fix_placeholders!(id_map)
-    self.members.map! do |type, id, role|
-      old_id = id.to_i
-      if old_id < 0
-        new_id = id_map[type.to_sym][old_id]
-        raise "invalid placeholder" if new_id.nil?
-        [type, new_id, role]
-      else
-        [type, id, role]
-      end
-    end
-  end
-
 end