Refactoring the amf controller, and all models so that save_with_history! is now private. Everyone should use create_with_history, delete_with_history, and update_from instead. Also gets the amf controller to do version checks in the node/way/relation models. Needs checked. This breaks Potlatch until #1376 is fixed.

This commit is contained in:
Shaun McDonald 2008-12-04 17:30:17 +00:00
parent d9c6758ee6
commit e9527b363d
6 changed files with 314 additions and 261 deletions

View file

@ -50,7 +50,9 @@ class Changeset < ActiveRecord::Base
end
def set_closed_time_now
self.closed_at = DateTime.now
unless is_open?
self.closed_at = DateTime.now
end
end
def self.from_xml(xml, create=false)

View file

@ -88,7 +88,7 @@ class Node < ActiveRecord::Base
raise OSM::APIBadUserInput.new("The node is outside this world") unless node.in_world?
# version must be present unless creating
return nil unless create or not pt['version'].nil?
raise OSM::APIBadXMLError.new("node", pt, "Version is required when updating") unless create or not pt['version'].nil?
node.version = create ? 0 : pt['version'].to_i
unless create
@ -119,42 +119,12 @@ class Node < ActiveRecord::Base
end
##
# the bounding box around a node
# the bounding box around a node, which is used for determining the changeset's
# bounding box
def bbox
[ longitude, latitude, longitude, latitude ]
end
def save_with_history!
t = Time.now
Node.transaction do
self.version += 1
self.timestamp = t
self.save!
# Create a NodeTag
tags = self.tags
NodeTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = NodeTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
# Create an OldNode
old_node = OldNode.from_node(self)
old_node.timestamp = t
old_node.save_with_dependencies!
# tell the changeset we updated one element only
changeset.add_changes! 1
# save the changeset in case of bounding box updates
changeset.save!
end
end
# Should probably be renamed delete_from to come in line with update
def delete_with_history!(new_node, user)
unless self.visible
@ -294,5 +264,38 @@ class Node < ActiveRecord::Base
def fix_placeholders!(id_map)
# nodes don't refer to anything, so there is nothing to do here
end
private
def save_with_history!
t = Time.now
Node.transaction do
self.version += 1
self.timestamp = t
self.save!
# Create a NodeTag
tags = self.tags
NodeTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = NodeTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
# Create an OldNode
old_node = OldNode.from_node(self)
old_node.timestamp = t
old_node.save_with_dependencies!
# tell the changeset we updated one element only
changeset.add_changes! 1
# save the changeset in case of bounding box updates
changeset.save!
end
end
end

View file

@ -214,6 +214,118 @@ class Relation < ActiveRecord::Base
@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,114 +448,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)
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
end

View file

@ -196,56 +196,6 @@ class Way < ActiveRecord::Base
[ lons.min, lats.min, lons.max, lats.max ]
end
def save_with_history!
t = Time.now
# update the bounding box, but don't save it as the controller knows the
# lifetime of the change better. note that this has to be done both before
# and after the save, so that nodes from both versions are included in the
# bbox.
changeset.update_bbox!(bbox) unless nodes.empty?
Way.transaction do
self.version += 1
self.timestamp = t
self.save!
tags = self.tags
WayTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
nds = self.nds
WayNode.delete_all(['id = ?', self.id])
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.id = [self.id, sequence]
nd.node_id = n
nd.save!
sequence += 1
end
old_way = OldWay.from_way(self)
old_way.timestamp = t
old_way.save_with_dependencies!
# update and commit the bounding box, now that way nodes
# have been updated and we're in a transaction.
changeset.update_bbox!(bbox) unless nodes.empty?
# tell the changeset we updated one element only
changeset.add_changes! 1
changeset.save!
end
end
def update_from(new_way, user)
check_consistency(self, new_way, user)
if !new_way.preconditions_ok?
@ -300,7 +250,7 @@ class Way < ActiveRecord::Base
self.tags = []
self.nds = []
self.visible = false
self.save_with_history!
save_with_history!
end
end
end
@ -358,4 +308,56 @@ class Way < ActiveRecord::Base
end
end
private
def save_with_history!
t = Time.now
# update the bounding box, but don't save it as the controller knows the
# lifetime of the change better. note that this has to be done both before
# and after the save, so that nodes from both versions are included in the
# bbox.
changeset.update_bbox!(bbox) unless nodes.empty?
Way.transaction do
self.version += 1
self.timestamp = t
self.save!
tags = self.tags
WayTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
nds = self.nds
WayNode.delete_all(['id = ?', self.id])
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.id = [self.id, sequence]
nd.node_id = n
nd.save!
sequence += 1
end
old_way = OldWay.from_way(self)
old_way.timestamp = t
old_way.save_with_dependencies!
# update and commit the bounding box, now that way nodes
# have been updated and we're in a transaction.
changeset.update_bbox!(bbox) unless nodes.empty?
# tell the changeset we updated one element only
changeset.add_changes! 1
changeset.save!
end
end
end