return
end
if node_ids.length == 0
- render :text => "<osm version='0.5'></osm>", :content_type => "text/xml"
+ render :text => "<osm version='#{API_VERSION}'></osm>", :content_type => "text/xml"
return
end
api = XML::Node.new 'api'
version = XML::Node.new 'version'
- version['minimum'] = '0.5';
- version['maximum'] = '0.5';
+ version['minimum'] = "#{API_VERSION}";
+ version['maximum'] = "#{API_VERSION}";
api << version
area = XML::Node.new 'area'
area['maximum'] = MAX_REQUEST_AREA.to_s;
# phrase from that, we can also put the error message into the status
# message. For now, rails won't let us)
def report_error(message)
- render :nothing => true, :status => :bad_request
+ render :text => message, :status => :bad_request
# Todo: some sort of escaping of problem characters in the message
response.headers['Error'] = message
end
def get_auth_data
if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it
authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
+ elsif request.env.has_key? 'REDIRECT_X_HTTP_AUTHORIZATION' # mod_fcgi
+ authdata = request.env['REDIRECT_X_HTTP_AUTHORIZATION'].to_s.split
elsif request.env.has_key? 'HTTP_AUTHORIZATION' # regular location
authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
end
--- /dev/null
+# The ChangesetController is the RESTful interface to Changeset objects
+
+class ChangesetController < ApplicationController
+ require 'xml/libxml'
+
+ before_filter :authorize, :only => [:create, :update, :delete, :upload]
+ before_filter :check_write_availability, :only => [:create, :update, :delete, :upload]
+ before_filter :check_read_availability, :except => [:create, :update, :delete, :upload]
+ after_filter :compress_output
+
+ # Create a changeset from XML.
+ def create
+ if request.put?
+ cs = Changeset.from_xml(request.raw_post, true)
+
+ if cs
+ cs.user_id = @user.id
+ cs.save_with_tags!
+ render :text => cs.id.to_s, :content_type => "text/plain"
+ else
+ render :nothing => true, :status => :bad_request
+ end
+ else
+ render :nothing => true, :status => :method_not_allowed
+ end
+ end
+
+ def create_prim(ids, prim, nd)
+ prim.version = 0
+ prim.user_id = @user.id
+ prim.visible = true
+ prim.save_with_history!
+
+ ids[nd['id'].to_i] = prim.id
+ end
+
+ def fix_way(w, node_ids)
+ w.nds = w.instance_eval { @nds }.
+ map { |nd| node_ids[nd] || nd }
+ return w
+ end
+
+ def fix_rel(r, ids)
+ r.members = r.instance_eval { @members }.
+ map { |memb| [memb[0], ids[memb[0]][memb[1].to_i] || memb[1], memb[2]] }
+ return r
+ end
+
+ def read
+ begin
+ changeset = Changeset.find(params[:id])
+ render :text => changeset.to_xml.to_s, :content_type => "text/xml"
+ rescue ActiveRecord::RecordNotFound
+ render :nothing => true, :status => :not_found
+ end
+ end
+
+ def close
+ begin
+ if not request.put?
+ render :nothing => true, :status => :method_not_allowed
+ return
+ end
+ changeset = Changeset.find(params[:id])
+ changeset.open = false
+ changeset.save
+ render :nothing => true
+ rescue ActiveRecord::RecordNotFound
+ render :nothing => true, :status => :not_found
+ end
+ end
+
+ def upload
+ if not request.put?
+ render :nothing => true, :status => :method_not_allowed
+ return
+ end
+
+ p = XML::Reader.new request.raw_post
+
+ node_ids, way_ids, rel_ids = {}, {}, {}
+ ids = {"node"=>node_ids, "way"=>way_ids, "relation"=>rel_ids}
+
+ models = {"node"=>Node, "way"=>Way, "relation"=>Relation}
+
+ res = XML::Document.new
+ res.encoding = 'UTF-8'
+ root = XML::Node.new 'osm'
+ root['version'] = '0.6'
+ root['creator'] = 'OpenStreetMap.org'
+ res.root = root
+
+ root << XML::Node.new_comment(" Warning: this is a 0.6 result document, " +
+ "not a normal OSM file. ")
+
+ Changeset.transaction do
+ while p.read == 1
+ break if p.node_type == 15 # end element
+ next unless p.node_type == 1 # element
+
+ case p.name
+ when 'create':
+ while p.read == 1
+ break if p.node_type == 15 # end element
+ next unless p.node_type == 1 # element
+
+ model = models[p.name]
+ next if model.nil?
+
+ elem = XML::Node.new p.name
+ nd = p.expand; p.next
+ osm = model.from_xml_node(nd, true)
+ elem['old_id'] = nd['id']
+
+ case nd.name
+ when 'way':
+ fix_way(osm, node_ids)
+ raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok?
+ when 'relation':
+ fix_rel(osm, ids)
+ raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok?
+ end
+
+ create_prim ids[nd.name], osm, nd
+ elem['new_id'] = osm.id.to_s
+ elem['new_version'] = osm.version.to_s
+ root << elem
+ end
+ when 'modify':
+ while p.read == 1
+ break if p.node_type == 15 # end element
+ next unless p.node_type == 1 # element
+
+ model = models[p.name]
+ next if model.nil?
+
+ elem = XML::Node.new p.name
+ new_osm = model.from_xml_node(p.expand); p.next
+ osm = model.find(new_osm.id)
+ osm.update_from new_osm, @user
+ elem['old_id'] = elem['new_id'] = osm.id.to_s
+ elem['new_version'] = osm.version.to_s
+ root << elem
+ end
+ when 'delete':
+ while p.read == 1
+ break if p.node_type == 15 # end element
+ next unless p.node_type == 1 # element
+
+ model = models[p.name]
+ next if model.nil?
+
+ elem = XML::Node.new p.name
+ osm = model.find(p.expand['id']); p.next
+ osm.delete_with_history(@user)
+ elem['old_id'] = elem['new_id'] = osm.id.to_s
+ elem['new_version'] = osm.version.to_s
+ root << elem
+ end
+ end
+ end
+ end
+
+ render :text => res.to_s, :content_type => "text/xml"
+
+ rescue OSM::APIError => ex
+ render ex.render_opts
+ end
+end
--- /dev/null
+class ChangesetTagController < ApplicationController
+ layout 'site'
+
+ def search
+ @tags = ChangesetTag.find(:all, :limit => 11, :conditions => ["match(v) against (?)", params[:query][:query].to_s] )
+ end
+
+
+end
node = Node.from_xml(request.raw_post, true)
if node
+ node.version = 0
node.user_id = @user.id
node.visible = true
node.save_with_history!
new_node = Node.from_xml(request.raw_post)
if new_node and new_node.id == node.id
- node.user_id = @user.id
- node.latitude = new_node.latitude
- node.longitude = new_node.longitude
- node.tags = new_node.tags
- node.visible = true
- node.save_with_history!
-
- render :nothing => true
+ node.update_from(new_node, @user)
+ render :text => node.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
+ rescue OSM::APIVersionMismatchError => ex
+ render :text => "Version mismatch: Provided " + ex.provided.to_s +
+ ", server had: " + ex.latest.to_s, :status => :bad_request
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
def delete
begin
node = Node.find(params[:id])
+ node.delete_with_history(@user)
- if node.visible
- if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", node.id ])
- render :text => "", :status => :precondition_failed
- elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", params[:id]])
- render :text => "", :status => :precondition_failed
- else
- node.user_id = @user.id
- node.visible = 0
- node.save_with_history!
-
- render :nothing => true
- end
- else
- render :text => "", :status => :gone
- end
+ render :nothing => true
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
+ rescue OSM::APIError => ex
+ render ex.render_opts
end
end
if !relation.preconditions_ok?
render :text => "", :status => :precondition_failed
else
+ relation.version = 0
relation.user_id = @user.id
relation.save_with_history!
new_relation = Relation.from_xml(request.raw_post)
if new_relation and new_relation.id == relation.id
- if !new_relation.preconditions_ok?
- render :text => "", :status => :precondition_failed
- else
- relation.user_id = @user.id
- relation.tags = new_relation.tags
- relation.members = new_relation.members
- relation.visible = true
- relation.save_with_history!
-
- render :nothing => true
- end
+ relation.update_from new_relation, user
+ render :text => relation.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
- rescue
- render :nothing => true, :status => :internal_server_error
+ rescue OSM::APIError => ex
+ render ex.render_opts
end
end
#XXX check if member somewhere!
begin
relation = Relation.find(params[:id])
-
- if relation.visible
- if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", params[:id]])
- render :text => "", :status => :precondition_failed
- else
- relation.user_id = @user.id
- relation.tags = []
- relation.members = []
- relation.visible = false
- relation.save_with_history!
-
- render :nothing => true
- end
- else
- render :text => "", :status => :gone
- end
+ relation.delete_with_history(@user)
+ rescue OSM::APIError => ex
+ render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
- rescue
- render :nothing => true, :status => :internal_server_error
end
end
if !way.preconditions_ok?
render :text => "", :status => :precondition_failed
else
+ way.version = 0
way.user_id = @user.id
way.save_with_history!
else
render :text => "", :status => :gone
end
+ rescue OSM::APIError => ex
+ render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
new_way = Way.from_xml(request.raw_post)
if new_way and new_way.id == way.id
- if !new_way.preconditions_ok?
- render :text => "", :status => :precondition_failed
- else
- way.user_id = @user.id
- way.tags = new_way.tags
- way.nds = new_way.nds
- way.visible = true
- way.save_with_history!
-
- render :nothing => true
- end
+ way.update_from(new_way, @user)
+ render :text => way.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
+ rescue OSM::APIError => ex
+ render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
def delete
begin
way = Way.find(params[:id])
- way.delete_with_relations_and_history(@user)
+ way.delete_with_history(@user)
# if we get here, all is fine, otherwise something will catch below.
render :nothing => true
- rescue OSM::APIAlreadyDeletedError
- render :text => "", :status => :gone
- rescue OSM::APIPreconditionFailedError
- render :text => "", :status => :precondition_failed
+ rescue OSM::APIError => ex
+ render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
--- /dev/null
+class Changeset < ActiveRecord::Base
+ require 'xml/libxml'
+
+ belongs_to :user
+
+ has_many :changeset_tags, :foreign_key => 'id'
+
+ def self.from_xml(xml, create=false)
+ begin
+ p = XML::Parser.new
+ p.string = xml
+ doc = p.parse
+
+ cs = Changeset.new
+
+ doc.find('//osm/changeset').each do |pt|
+ if create
+ cs.created_at = Time.now
+ end
+
+ pt.find('tag').each do |tag|
+ cs.add_tag_keyval(tag['k'], tag['v'])
+ end
+ end
+ rescue Exception => ex
+ print "noes "+ ex.to_s + "\n"
+ cs = nil
+ end
+
+ return cs
+ end
+
+ def tags
+ unless @tags
+ @tags = {}
+ self.changeset_tags.each do |tag|
+ @tags[tag.k] = tag.v
+ end
+ end
+ @tags
+ end
+
+ def tags=(t)
+ @tags = t
+ end
+
+ def add_tag_keyval(k, v)
+ @tags = Hash.new unless @tags
+ @tags[k] = v
+ end
+
+ def save_with_tags!
+ t = Time.now
+
+ Changeset.transaction do
+ # fixme update modified_at time?
+ self.save!
+ end
+
+ ChangesetTag.transaction do
+ tags = self.tags
+ ChangesetTag.delete_all(['id = ?', self.id])
+
+ tags.each do |k,v|
+ tag = ChangesetTag.new
+ tag.k = k
+ tag.v = v
+ tag.id = self.id
+ tag.save!
+ end
+ end
+ end
+
+ def to_xml
+ doc = OSM::API.new.get_xml_doc
+ doc.root << to_xml_node()
+ return doc
+ end
+ def to_xml_node(user_display_name_cache = nil)
+ el1 = XML::Node.new 'changeset'
+ el1['id'] = self.id.to_s
+
+ user_display_name_cache = {} if user_display_name_cache.nil?
+
+ if user_display_name_cache and user_display_name_cache.key?(self.user_id)
+ # use the cache if available
+ elsif self.user.data_public?
+ user_display_name_cache[self.user_id] = self.user.display_name
+ else
+ user_display_name_cache[self.user_id] = nil
+ end
+
+ el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
+
+ self.tags.each do |k,v|
+ el2 = XML::Node.new('tag')
+ el2['k'] = k.to_s
+ el2['v'] = v.to_s
+ el1 << el2
+ end
+
+ el1['created_at'] = self.created_at.xmlschema
+ el1['open'] = self.open.to_s
+
+ # FIXME FIXME FIXME: This does not include changes yet! There is
+ # currently no changeset_id column in the tables as far as I can tell,
+ # so this is just a scaffold to build on, not a complete to_xml
+
+ return el1
+ end
+end
--- /dev/null
+class ChangesetTag < ActiveRecord::Base
+
+ belongs_to :changeset, :foreign_key => 'id'
+
+end
belongs_to :sender, :class_name => "User", :foreign_key => :from_user_id
belongs_to :recipient, :class_name => "User", :foreign_key => :to_user_id
- validates_presence_of :title, :body, :sent_on
+ validates_presence_of :title, :body, :sent_on, :sender, :recipient
validates_inclusion_of :message_read, :in => [ true, false ]
validates_associated :sender, :recipient
end
has_many :way_nodes
has_many :ways, :through => :way_nodes
+ has_many :node_tags, :foreign_key => :id
+
has_many :old_way_nodes
has_many :ways_via_history, :class_name=> "Way", :through => :old_way_nodes, :source => :way
p = XML::Parser.new
p.string = xml
doc = p.parse
-
- node = Node.new
doc.find('//osm/node').each do |pt|
- node.lat = pt['lat'].to_f
- node.lon = pt['lon'].to_f
+ return Node.from_xml_node(pt, create)
+ end
+ rescue
+ return nil
+ end
+ end
- return nil unless node.in_world?
+ def self.from_xml_node(pt, create=false)
+ node = Node.new
+
+ node.version = pt['version']
+ node.lat = pt['lat'].to_f
+ node.lon = pt['lon'].to_f
- unless create
- if pt['id'] != '0'
- node.id = pt['id'].to_i
- end
- end
+ return nil unless node.in_world?
- node.visible = pt['visible'] and pt['visible'] == 'true'
+ unless create
+ if pt['id'] != '0'
+ node.id = pt['id'].to_i
+ end
+ end
- if create
- node.timestamp = Time.now
- else
- if pt['timestamp']
- node.timestamp = Time.parse(pt['timestamp'])
- end
- end
+ node.visible = pt['visible'] and pt['visible'] == 'true'
- tags = []
+ if create
+ node.timestamp = Time.now
+ else
+ if pt['timestamp']
+ node.timestamp = Time.parse(pt['timestamp'])
+ end
+ end
- pt.find('tag').each do |tag|
- tags << [tag['k'],tag['v']]
- end
+ tags = []
- node.tags = Tags.join(tags)
- end
- rescue
- node = nil
+ pt.find('tag').each do |tag|
+ node.add_tag_key_val(tag['k'],tag['v'])
end
return node
end
- # Save this node with the appropriate OldNode object to represent it's history.
def save_with_history!
+ t = Time.now
Node.transaction do
- self.timestamp = Time.now
+ 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.save!
+ old_node.timestamp = t
+ old_node.save_with_dependencies!
+ end
+ end
+
+ def delete_with_history(user)
+ if self.visible
+ if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", self.id ])
+ raise OSM::APIPreconditionFailedError.new
+ elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", self.id])
+ raise OSM::APIPreconditionFailedError.new
+ else
+ self.user_id = user.id
+ self.visible = 0
+ save_with_history!
+ end
+ else
+ raise OSM::APIAlreadyDeletedError.new
+ end
+ end
+
+ def update_from(new_node, user)
+ if new_node.version != version
+ raise OSM::APIVersionMismatchError.new(new_node.version, version)
end
+
+ self.user_id = user.id
+ self.latitude = new_node.latitude
+ self.longitude = new_node.longitude
+ self.tags = new_node.tags
+ self.visible = true
+ save_with_history!
end
- # Turn this Node in to a complete OSM XML object with <osm> wrapper
def to_xml
doc = OSM::API.new.get_xml_doc
doc.root << to_xml_node()
return doc
end
- # Turn this Node in to an XML Node without the <osm> wrapper.
def to_xml_node(user_display_name_cache = nil)
el1 = XML::Node.new 'node'
el1['id'] = self.id.to_s
el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
- Tags.split(self.tags) do |k,v|
+ self.tags.each do |k,v|
el2 = XML::Node.new('tag')
el2['k'] = k.to_s
el2['v'] = v.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
return el1
end
- # Return the node's tags as a Hash of keys and their values
def tags_as_hash
- hash = {}
- Tags.split(self.tags) do |k,v|
- hash[k] = v
+ return tags
+ end
+
+ def tags
+ unless @tags
+ @tags = {}
+ self.node_tags.each do |tag|
+ @tags[tag.k] = tag.v
+ end
end
- hash
+ @tags
+ end
+
+ def tags=(t)
+ @tags = t
+ end
+
+ def add_tag_key_val(k,v)
+ @tags = Hash.new unless @tags
+ @tags[k] = v
end
+
+
+
end
--- /dev/null
+class NodeTag < ActiveRecord::Base
+ set_table_name 'current_node_tags'
+
+ belongs_to :node, :foreign_key => 'id'
+end
errors.add_to_base("Node is not in the world") unless in_world?
end
- def in_world?
- return false if self.lat < -90 or self.lat > 90
- return false if self.lon < -180 or self.lon > 180
- return true
- end
-
def self.from_node(node)
old_node = OldNode.new
old_node.latitude = node.latitude
old_node.timestamp = node.timestamp
old_node.user_id = node.user_id
old_node.id = node.id
+ old_node.version = node.version
return old_node
end
+
+ def to_xml
+ doc = OSM::API.new.get_xml_doc
+ doc.root << to_xml_node()
+ return doc
+ end
def to_xml_node
el1 = XML::Node.new 'node'
el1['lon'] = self.lon.to_s
el1['user'] = self.user.display_name if self.user.data_public?
- Tags.split(self.tags) do |k,v|
+ self.tags.each do |k,v|
el2 = XML::Node.new('tag')
el2['k'] = k.to_s
el2['v'] = v.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
return el1
end
-
- def tags_as_hash
- hash = {}
- Tags.split(self.tags) do |k,v|
- hash[k] = v
+
+ def save_with_dependencies!
+ save!
+ #not sure whats going on here
+ clear_aggregation_cache
+ clear_association_cache
+ #ok from here
+ @attributes.update(OldNode.find(:first, :conditions => ['id = ? AND timestamp = ?', self.id, self.timestamp]).instance_variable_get('@attributes'))
+
+ self.tags.each do |k,v|
+ tag = OldNodeTag.new
+ tag.k = k
+ tag.v = v
+ tag.id = self.id
+ tag.version = self.version
+ tag.save!
end
- hash
end
- # Pretend we're not in any ways
- def ways
- return []
+ def tags
+ unless @tags
+ @tags = Hash.new
+ OldNodeTag.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |tag|
+ @tags[tag.k] = tag.v
+ end
+ end
+ @tags = Hash.new unless @tags
+ @tags
end
- # Pretend we're not in any relations
- def containing_relation_members
- return []
+ def tags=(t)
+ @tags = t
end
+
+ def tags_as_hash
+ hash = {}
+ Tags.split(self.tags) do |k,v|
+ hash[k] = v
+ end
+ hash
+ end
+
+ # Pretend we're not in any ways
+ def ways
+ return []
+ end
+
+ # Pretend we're not in any relations
+ def containing_relation_members
+ return []
+ end
end
--- /dev/null
+class OldNodeTag < ActiveRecord::Base
+ belongs_to :user
+
+ set_table_name 'node_tags'
+
+
+end
old_relation.user_id = relation.user_id
old_relation.timestamp = relation.timestamp
old_relation.id = relation.id
+ old_relation.version = relation.version
old_relation.members = relation.members
old_relation.tags = relation.tags
return old_relation
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['user'] = self.user.display_name if self.user.data_public?
+ el1['version'] = self.version.to_s
self.old_members.each do |member|
e = XML::Node.new 'member'
old_way.user_id = way.user_id
old_way.timestamp = way.timestamp
old_way.id = way.id
+ old_way.version = way.version
old_way.nds = way.nds
old_way.tags = way.tags
return old_way
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['user'] = self.user.display_name if self.user.data_public?
+ el1['version'] = self.version.to_s
self.old_nodes.each do |nd| # FIXME need to make sure they come back in the right order
e = XML::Node.new 'nd'
p.string = xml
doc = p.parse
- relation = Relation.new
-
doc.find('//osm/relation').each do |pt|
- if !create and pt['id'] != '0'
- relation.id = pt['id'].to_i
- end
+ return Relation.from_xml_node(pt, create)
+ end
+ rescue
+ return nil
+ end
+ end
- if create
- relation.timestamp = Time.now
- relation.visible = true
- else
- if pt['timestamp']
- relation.timestamp = Time.parse(pt['timestamp'])
- end
- end
+ def self.from_xml_node(pt, create=false)
+ relation = Relation.new
- pt.find('tag').each do |tag|
- relation.add_tag_keyval(tag['k'], tag['v'])
- end
+ if !create and pt['id'] != '0'
+ relation.id = pt['id'].to_i
+ end
- pt.find('member').each do |member|
- relation.add_member(member['type'], member['ref'], member['role'])
- end
+ relation.version = pt['version']
+
+ if create
+ relation.timestamp = Time.now
+ relation.visible = true
+ else
+ if pt['timestamp']
+ relation.timestamp = Time.parse(pt['timestamp'])
end
- rescue
- relation = nil
+ end
+
+ pt.find('tag').each do |tag|
+ relation.add_tag_keyval(tag['k'], tag['v'])
+ end
+
+ pt.find('member').each do |member|
+ relation.add_member(member['type'], member['ref'], member['role'])
end
return relation
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
user_display_name_cache = {} if user_display_name_cache.nil?
def save_with_history!
Relation.transaction do
t = Time.now
+ self.version += 1
self.timestamp = t
self.save!
tags = self.tags
-
RelationTag.delete_all(['id = ?', self.id])
-
tags.each do |k,v|
tag = RelationTag.new
tag.k = k
end
members = self.members
-
RelationMember.delete_all(['id = ?', self.id])
-
members.each do |n|
mem = RelationMember.new
mem.id = self.id
end
end
+ def delete_with_history(user)
+ if self.visible
+ if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", self.id ])
+ raise OSM::APIPreconditionFailedError.new
+ else
+ self.user_id = user.id
+ self.tags = []
+ self.members = []
+ self.visible = false
+ save_with_history!
+ end
+ else
+ raise OSM::APIAlreadyDeletedError.new
+ end
+ end
+
+ def update_from(new_relation, user)
+ if !new_relation.preconditions_ok?
+ raise OSM::APIPreconditionFailedError.new
+ elsif new_relation.version != version
+ raise OSM::APIVersionMismatchError.new(new_relation.version, version)
+ else
+ self.user_id = user.id
+ self.tags = new_relation.tags
+ self.members = new_relation.members
+ self.visible = true
+ save_with_history!
+ end
+ 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.
p.string = xml
doc = p.parse
- way = Way.new
-
doc.find('//osm/way').each do |pt|
- if !create and pt['id'] != '0'
- way.id = pt['id'].to_i
- end
+ return Way.from_xml_node(pt, create)
+ end
+ rescue
+ return nil
+ end
+ end
- if create
- way.timestamp = Time.now
- way.visible = true
- else
- if pt['timestamp']
- way.timestamp = Time.parse(pt['timestamp'])
- end
- end
+ def self.from_xml_node(pt, create=false)
+ way = Way.new
- pt.find('tag').each do |tag|
- way.add_tag_keyval(tag['k'], tag['v'])
- end
+ if !create and pt['id'] != '0'
+ way.id = pt['id'].to_i
+ end
+
+ way.version = pt['version']
- pt.find('nd').each do |nd|
- way.add_nd_num(nd['ref'])
- end
+ if create
+ way.timestamp = Time.now
+ way.visible = true
+ else
+ if pt['timestamp']
+ way.timestamp = Time.parse(pt['timestamp'])
end
- rescue
- way = nil
+ end
+
+ pt.find('tag').each do |tag|
+ way.add_tag_keyval(tag['k'], tag['v'])
+ end
+
+ pt.find('nd').each do |nd|
+ way.add_nd_num(nd['ref'])
end
return way
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
user_display_name_cache = {} if user_display_name_cache.nil?
t = Time.now
Way.transaction do
+ self.version += 1
self.timestamp = t
self.save!
- end
- WayTag.transaction do
tags = self.tags
-
WayTag.delete_all(['id = ?', self.id])
-
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.id = self.id
tag.save!
end
- end
- WayNode.transaction do
nds = self.nds
-
WayNode.delete_all(['id = ?', self.id])
-
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.save!
sequence += 1
end
+
+ old_way = OldWay.from_way(self)
+ old_way.timestamp = t
+ old_way.save_with_dependencies!
end
+ end
- old_way = OldWay.from_way(self)
- old_way.timestamp = t
- old_way.save_with_dependencies!
+ def update_from(new_way, user)
+ if !new_way.preconditions_ok?
+ raise OSM::APIPreconditionFailedError.new
+ elsif new_way.version != version
+ raise OSM::APIVersionMismatchError.new(new_way.version, version)
+ else
+ self.user_id = user.id
+ self.tags = new_way.tags
+ self.nds = new_way.nds
+ self.visible = true
+ save_with_history!
+ end
end
def preconditions_ok?
return true
end
- # Delete the way and it's relations, but don't really delete it - set its visibility to false and update the history etc to maintain wiki-like functionality.
- def delete_with_relations_and_history(user)
+ def delete_with_history(user)
if self.visible
# FIXME
# this should actually delete the relations,
# not just throw a PreconditionFailed if it's a member of a relation!!
+
+ # FIXME: this should probably renamed to delete_with_history
if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id",
:conditions => [ "visible = 1 AND member_type='way' and member_id=?", self.id])
raise OSM::APIPreconditionFailedError
end
# delete a way and it's nodes that aren't part of other ways, with history
+
+ # FIXME: merge the potlatch code to delete the relations
def delete_with_relations_and_nodes_and_history(user)
# delete the nodes not used by other ways
self.unshared_node_ids.each do |node_id|
self.user_id = user.id
- self.delete_with_relations_and_history(user)
+ self.delete_with_history(user)
end
# Find nodes that belong to this way only
if (size > 0.25) {
setStatus("Unable to load: Bounding box size of " + size + " is too large (must be smaller than 0.25)");
} else {
- loadGML("/api/0.5/map?bbox=" + projected.toBBOX());
+ loadGML("/api/#{API_VERSION}/map?bbox=" + projected.toBBOX());
}
}
this.link.href = "";
this.link.innerHTML = "Wait...";
- new Ajax.Request("/api/0.5/" + this.type + "/" + this.feature.osm_id + "/history", {
+ new Ajax.Request("/api/#{API_VERSION}/" + this.type + "/" + this.feature.osm_id + "/history", {
onComplete: OpenLayers.Function.bind(displayHistory, this)
});
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
adapter: mysql
- database: openstreetmap
- username: openstreetmap
- password: openstreetmap
+ #database: openstreetmap
+ #username: openstreetmap
+ #password: openstreetmap
+ database: osm_test
+ username: osm_test
+ password: osm_test
host: localhost
# Warning: The database defined as 'test' will be erased and
test:
adapter: mysql
database: osm_test
- username: root
- password:
+ username: osm_test
+ password: osm_test
host: localhost
production:
adapter: mysql
- database: openstreetmap
- username: openstreetmap
- password: openstreetmap
- host: db.openstreetmap.org
+ database: osm
+ username: osm
+ password: osm
+ host: localhost
SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org'
# Application constants needed for routes.rb - must go before Initializer call
-API_VERSION = ENV['OSM_API_VERSION'] || '0.5'
+API_VERSION = ENV['OSM_API_VERSION'] || '0.6'
# Set application status - possible settings are:
#
ActionController::Routing::Routes.draw do |map|
# API
+ map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create'
+ map.connect "api/#{API_VERSION}/changeset/upload", :controller => 'changeset', :action => 'upload'
+ map.connect "api/#{API_VERSION}/changeset/:id", :controller => 'changeset', :action => 'read', :id => /\d+/
+ map.connect "api/#{API_VERSION}/changeset/:id/close", :controller => 'changeset', :action => 'close', :id =>/\d+/
+
map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create'
map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/
map.connect "api/#{API_VERSION}/node/:id/relations", :controller => 'relation', :action => 'relations_for_node', :id => /\d+/
# Potlatch API
+ map.connect "api/0.5/amf", :controller =>'amf', :action =>'talk'
map.connect "api/#{API_VERSION}/amf", :controller =>'amf', :action =>'talk'
map.connect "api/#{API_VERSION}/swf/trackpoints", :controller =>'swf', :action =>'trackpoints'
--- /dev/null
+class AddTimestampIndexes < ActiveRecord::Migration
+ def self.up
+ add_index :current_ways, :timestamp, :name => :current_ways_timestamp_idx
+ add_index :current_relations, :timestamp, :name => :current_relations_timestamp_idx
+ end
+
+ def self.down
+ remove_index :current_ways, :name => :current_ways_timestamp_idx
+ remove_index :current_relations, :name => :current_relations_timestamp_idx
+ end
+end
--- /dev/null
+class PopulateNodeTagsAndRemove < ActiveRecord::Migration
+ def self.up
+ have_nodes = select_value("SELECT count(*) FROM current_nodes").to_i != 0
+
+ if have_nodes
+ prefix = File.join Dir.tmpdir, "013_populate_node_tags_and_remove.#{$$}."
+
+ cmd = "db/migrate/013_populate_node_tags_and_remove_helper"
+ src = "#{cmd}.c"
+ if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then
+ system 'cc -O3 -Wall `mysql_config --cflags --libs` ' +
+ "#{src} -o #{cmd}" or fail
+ end
+
+ conn_opts = ActiveRecord::Base.connection.
+ instance_eval { @connection_options }
+ args = conn_opts.map { |arg| arg.to_s } + [prefix]
+ fail "#{cmd} failed" unless system cmd, *args
+
+ tempfiles = ['nodes', 'node_tags',
+ 'current_nodes', 'current_node_tags'].
+ map { |base| prefix + base }
+ nodes, node_tags, current_nodes, current_node_tags = tempfiles
+ end
+
+ execute "TRUNCATE nodes"
+ remove_column :nodes, :tags
+ remove_column :current_nodes, :tags
+
+ add_column :nodes, :version, :bigint, :limit => 20, :null => false
+
+ create_table :current_node_tags, innodb_table do |t|
+ t.column :id, :bigint, :limit => 64, :null => false
+ t.column :k, :string, :default => "", :null => false
+ t.column :v, :string, :default => "", :null => false
+ end
+
+ create_table :node_tags, innodb_table do |t|
+ t.column :id, :bigint, :limit => 64, :null => false
+ t.column :version, :bigint, :limit => 20, :null => false
+ t.column :k, :string, :default => "", :null => false
+ t.column :v, :string, :default => "", :null => false
+ end
+
+ # now get the data back
+ csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'"
+
+ if have_nodes
+ execute "LOAD DATA INFILE '#{nodes}' INTO TABLE nodes #{csvopts} (id, latitude, longitude, user_id, visible, timestamp, tile, version)";
+ execute "LOAD DATA INFILE '#{node_tags}' INTO TABLE node_tags #{csvopts} (id, version, k, v)"
+ execute "LOAD DATA INFILE '#{current_node_tags}' INTO TABLE current_node_tags #{csvopts} (id, k, v)"
+ end
+
+ tempfiles.each { |fn| File.unlink fn } if have_nodes
+ end
+
+ def self.down
+ raise IrreversibleMigration.new
+# add_column :nodes, "tags", :text, :default => "", :null => false
+# add_column :current_nodes, "tags", :text, :default => "", :null => false
+ end
+end
--- /dev/null
+#include <mysql.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void exit_mysql_err(MYSQL *mysql) {
+ const char *err = mysql_error(mysql);
+ if (err) {
+ fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
+ } else {
+ fprintf(stderr, "013_populate_node_tags_and_remove_helper: MySQL error\n");
+ }
+ abort();
+ exit(EXIT_FAILURE);
+}
+
+static void write_csv_col(FILE *f, const char *str, char end) {
+ char *out = (char *) malloc(2 * strlen(str) + 4);
+ char *o = out;
+ size_t len;
+
+ *(o++) = '\"';
+ for (; *str; str++) {
+ if (*str == '\0') {
+ break;
+ } else if (*str == '\"') {
+ *(o++) = '\"';
+ *(o++) = '\"';
+ } else {
+ *(o++) = *str;
+ }
+ }
+ *(o++) = '\"';
+ *(o++) = end;
+ *(o++) = '\0';
+
+ len = strlen(out);
+ if (fwrite(out, len, 1, f) != 1) {
+ perror("fwrite");
+ exit(EXIT_FAILURE);
+ }
+
+ free(out);
+}
+
+static void unescape(char *str) {
+ char *i = str, *o = str, tmp;
+
+ while (*i) {
+ if (*i == '\\') {
+ i++;
+ switch (tmp = *i++) {
+ case 's': *o++ = ';'; break;
+ case 'e': *o++ = '='; break;
+ case '\\': *o++ = '\\'; break;
+ default: *o++ = tmp; break;
+ }
+ } else {
+ *o++ = *i++;
+ }
+ }
+}
+
+static int read_node_tags(char **tags, char **k, char **v) {
+ if (!**tags) return 0;
+ char *i = strchr(*tags, ';');
+ if (!i) i = *tags + strlen(*tags);
+ char *j = strchr(*tags, '=');
+ *k = *tags;
+ if (j && j < i) {
+ *v = j + 1;
+ } else {
+ *v = i;
+ }
+ *tags = *i ? i + 1 : i;
+ *i = '\0';
+ if (j) *j = '\0';
+
+ unescape(*k);
+ unescape(*v);
+
+ return 1;
+}
+
+struct data {
+ MYSQL *mysql;
+ size_t version_size;
+ uint16_t *version;
+};
+
+static void proc_nodes(struct data *d, const char *tbl, FILE *out, FILE *out_tags, int hist) {
+ MYSQL_RES *res;
+ MYSQL_ROW row;
+ char query[256];
+
+ snprintf(query, sizeof(query), "SELECT id, latitude, longitude, "
+ "user_id, visible, tags, timestamp, tile FROM %s", tbl);
+ if (mysql_query(d->mysql, query))
+ exit_mysql_err(d->mysql);
+
+ res = mysql_use_result(d->mysql);
+ if (!res) exit_mysql_err(d->mysql);
+
+ while ((row = mysql_fetch_row(res))) {
+ unsigned long id = strtoul(row[0], NULL, 10);
+ uint32_t version;
+
+ if (id >= d->version_size) {
+ fprintf(stderr, "preallocated nodes size exceeded");
+ abort();
+ }
+
+ if (hist) {
+ version = ++(d->version[id]);
+
+ fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%u\"\n",
+ row[0], row[1], row[2], row[3], row[4], row[6], row[7], version);
+ } else {
+ /*fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
+ row[0], row[1], row[2], row[3], row[4], row[6], row[7]);*/
+ }
+
+ char *tags_it = row[5], *k, *v;
+ while (read_node_tags(&tags_it, &k, &v)) {
+ if (hist) {
+ fprintf(out_tags, "\"%s\",\"%u\",", row[0], version);
+ } else {
+ fprintf(out_tags, "\"%s\",", row[0]);
+ }
+
+ write_csv_col(out_tags, k, ',');
+ write_csv_col(out_tags, v, '\n');
+ }
+ }
+ if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
+
+ mysql_free_result(res);
+}
+
+static size_t select_size(MYSQL *mysql, const char *q) {
+ MYSQL_RES *res;
+ MYSQL_ROW row;
+ size_t ret;
+
+ if (mysql_query(mysql, q))
+ exit_mysql_err(mysql);
+
+ res = mysql_store_result(mysql);
+ if (!res) exit_mysql_err(mysql);
+
+ row = mysql_fetch_row(res);
+ if (!row) exit_mysql_err(mysql);
+
+ if (row[0]) {
+ ret = strtoul(row[0], NULL, 10);
+ } else {
+ ret = 0;
+ }
+
+ mysql_free_result(res);
+
+ return ret;
+}
+
+static MYSQL *connect_to_mysql(char **argv) {
+ MYSQL *mysql = mysql_init(NULL);
+ if (!mysql) exit_mysql_err(mysql);
+
+ if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4],
+ argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0))
+ exit_mysql_err(mysql);
+
+ if (mysql_set_character_set(mysql, "utf8"))
+ exit_mysql_err(mysql);
+
+ return mysql;
+}
+
+static void open_file(FILE **f, char *fn) {
+ *f = fopen(fn, "w+");
+ if (!*f) {
+ perror("fopen");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char **argv) {
+ size_t prefix_len;
+ FILE *current_nodes, *current_node_tags, *nodes, *node_tags;
+ char *tempfn;
+ struct data data, *d = &data;
+
+ if (argc != 8) {
+ printf("Usage: 013_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n");
+ exit(EXIT_FAILURE);
+ }
+
+ d->mysql = connect_to_mysql(argv);
+
+ d->version_size = 1 + select_size(d->mysql, "SELECT max(id) FROM current_nodes");
+ d->version = (uint16_t *) malloc(sizeof(uint16_t) * d->version_size);
+ if (!d->version) {
+ perror("malloc");
+ abort();
+ exit(EXIT_FAILURE);
+ }
+ memset(d->version, 0, sizeof(uint16_t) * d->version_size);
+
+ prefix_len = strlen(argv[7]);
+ tempfn = (char *) malloc(prefix_len + 32);
+ strcpy(tempfn, argv[7]);
+
+ strcpy(tempfn + prefix_len, "current_nodes");
+ open_file(¤t_nodes, tempfn);
+
+ strcpy(tempfn + prefix_len, "current_node_tags");
+ open_file(¤t_node_tags, tempfn);
+
+ strcpy(tempfn + prefix_len, "nodes");
+ open_file(&nodes, tempfn);
+
+ strcpy(tempfn + prefix_len, "node_tags");
+ open_file(&node_tags, tempfn);
+
+ free(tempfn);
+
+ proc_nodes(d, "nodes", nodes, node_tags, 1);
+ proc_nodes(d, "current_nodes", current_nodes, current_node_tags, 0);
+
+ free(d->version);
+
+ mysql_close(d->mysql);
+
+ fclose(current_nodes);
+ fclose(current_node_tags);
+ fclose(nodes);
+ fclose(node_tags);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+class MoveToInnodb < ActiveRecord::Migration
+ @@conv_tables = ['nodes', 'ways', 'way_tags', 'way_nodes',
+ 'current_way_tags', 'relation_members',
+ 'relations', 'relation_tags', 'current_relation_tags']
+
+ @@ver_tbl = ['nodes', 'ways', 'relations']
+
+ def self.up
+ execute 'DROP INDEX current_way_tags_v_idx ON current_way_tags'
+ execute 'DROP INDEX current_relation_tags_v_idx ON current_relation_tags'
+
+ @@ver_tbl.each { |tbl|
+ change_column tbl, "version", :bigint, :limit => 20, :null => false
+ }
+
+ @@conv_tables.each { |tbl|
+ execute "ALTER TABLE #{tbl} ENGINE = InnoDB"
+ }
+
+ @@ver_tbl.each { |tbl|
+ add_column "current_#{tbl}", "version", :bigint, :limit => 20, :null => false
+ execute "UPDATE current_#{tbl} SET version = " +
+ "(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)"
+ }
+ end
+
+ def self.down
+ raise IrreversibleMigration.new
+ end
+end
--- /dev/null
+class KeyConstraints < ActiveRecord::Migration
+ def self.up
+ # Primary keys
+ add_primary_key :current_node_tags, [:id, :k]
+ add_primary_key :current_way_tags, [:id, :k]
+ add_primary_key :current_relation_tags, [:id, :k]
+
+ add_primary_key :node_tags, [:id, :version, :k]
+ add_primary_key :way_tags, [:id, :version, :k]
+ add_primary_key :relation_tags, [:id, :version, :k]
+
+ add_primary_key :nodes, [:id, :version]
+
+ # Remove indexes superseded by primary keys
+ remove_index :current_way_tags, :name => :current_way_tags_id_idx
+ remove_index :current_relation_tags, :name => :current_relation_tags_id_idx
+
+ remove_index :way_tags, :name => :way_tags_id_version_idx
+ remove_index :relation_tags, :name => :relation_tags_id_version_idx
+
+ remove_index :nodes, :name => :nodes_uid_idx
+
+ # Foreign keys (between ways, way_tags, way_nodes, etc.)
+ add_foreign_key :current_node_tags, [:id], :current_nodes
+ add_foreign_key :node_tags, [:id, :version], :nodes
+
+ add_foreign_key :current_way_tags, [:id], :current_ways
+ add_foreign_key :current_way_nodes, [:id], :current_ways
+ add_foreign_key :way_tags, [:id, :version], :ways
+ add_foreign_key :way_nodes, [:id, :version], :ways
+
+ add_foreign_key :current_relation_tags, [:id], :current_relations
+ add_foreign_key :current_relation_members, [:id], :current_relations
+ add_foreign_key :relation_tags, [:id, :version], :relations
+ add_foreign_key :relation_members, [:id, :version], :relations
+
+ # Foreign keys (between different types of primitives)
+ add_foreign_key :current_way_nodes, [:node_id], :current_nodes, [:id]
+
+ # FIXME: We don't have foreign keys for relation members since the id
+ # might point to a different table depending on the `type' column.
+ # We'd probably need different current_relation_member_nodes,
+ # current_relation_member_ways and current_relation_member_relations
+ # tables for this to work cleanly.
+ end
+
+ def self.down
+ raise IrreversibleMigration.new
+ end
+end
--- /dev/null
+class AddChangesets < ActiveRecord::Migration
+ def self.up
+ create_table "changesets", innodb_table do |t|
+ t.column "id", :bigint, :limit => 20, :null => false
+ t.column "user_id", :bigint, :limit => 20, :null => false
+ t.column "created_at", :datetime, :null => false
+ t.column "open", :boolean, :null => false, :default => true
+ t.column "min_lat", :integer, :null => true
+ t.column "max_lat", :integer, :null => true
+ t.column "min_lon", :integer, :null => true
+ t.column "max_lon", :integer, :null => true
+ end
+
+ add_primary_key "changesets", ["id"]
+ # FIXME add indexes?
+
+ change_column "changesets", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
+
+ create_table "changeset_tags", innodb_table do |t|
+ t.column "id", :bigint, :limit => 64, :null => false
+ t.column "k", :string, :default => "", :null => false
+ t.column "v", :string, :default => "", :null => false
+ end
+
+ add_index "changeset_tags", ["id"], :name => "changeset_tags_id_idx"
+ end
+
+ def self.down
+ drop_table "changesets"
+ drop_table "changeset_tags"
+ end
+end
module GeoRecord
+ # This scaling factor is used to convert between the float lat/lon that is
+ # returned by the API, and the integer lat/lon equivalent that is stored in
+ # the database.
+ SCALE = 10000000
+
def self.included(base)
base.extend(ClassMethods)
end
end
def lat=(l)
- self.latitude = (l * 10000000).round
+ self.latitude = (l * SCALE).round
end
def lon=(l)
- self.longitude = (l * 10000000).round
+ self.longitude = (l * SCALE).round
end
# Return WGS84 latitude
def lat
- return self.latitude.to_f / 10000000
+ return self.latitude.to_f / SCALE
end
# Return WGS84 longitude
def lon
- return self.longitude.to_f / 10000000
+ return self.longitude.to_f / SCALE
end
private
module ActiveRecord
module ConnectionAdapters
module SchemaStatements
+ def quote_column_names(column_name)
+ Array(column_name).map { |e| quote_column_name(e) }.join(", ")
+ end
+
def add_primary_key(table_name, column_name, options = {})
column_names = Array(column_name)
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
execute "ALTER TABLE #{table_name} DROP PRIMARY KEY"
end
+ def add_foreign_key(table_name, column_name, reftbl, refcol = nil)
+ execute "ALTER TABLE #{table_name} ADD " +
+ "FOREIGN KEY (#{quote_column_names(column_name)}) " +
+ "REFERENCES #{reftbl} (#{quote_column_names(refcol || column_name)})"
+ end
+
alias_method :old_options_include_default?, :options_include_default?
def options_include_default?(options)
# The base class for API Errors.
class APIError < RuntimeError
+ def render_opts
+ { :text => "", :status => :internal_server_error }
+ end
end
# Raised when an API object is not found.
# Raised when a precondition to an API action fails sanity check.
class APIPreconditionFailedError < APIError
+ def render_opts
+ { :text => "", :status => :precondition_failed }
+ end
end
# Raised when to delete an already-deleted object.
class APIAlreadyDeletedError < APIError
+ def render_opts
+ { :text => "", :status => :gone }
+ end
+ end
+
+ # Raised when the provided version is not equal to the latest in the db.
+ class APIVersionMismatchError < APIError
+ def initialize(provided, latest)
+ @provided, @latest = provided, latest
+ end
+
+ attr_reader :provided, :latest
+
+ def render_opts
+ { :text => "Version mismatch: Provided " + provided.to_s +
+ ", server had: " + latest.to_s, :status => :bad_request }
+ end
end
# Helper methods for going to/from mercator and lat/lng.
+++ /dev/null
-namespace 'db' do
- desc 'Populate the node_tags table'
- task :node_tags do
- require File.dirname(__FILE__) + '/../../config/environment'
-
- node_count = Node.count
- limit = 1000 #the number of nodes to grab in one go
- offset = 0
-
- while offset < node_count
- Node.find(:all, :limit => limit, :offset => offset).each do |node|
- seq_id = 1
- node.tags.split(';').each do |tag|
- nt = NodeTag.new
- nt.id = node.id
- nt.k = tag.split('=')[0] || ''
- nt.v = tag.split('=')[1] || ''
- nt.sequence_id = seq_id
- nt.save! || raise
- seq_id += 1
- end
-
- version = 1 #version refers to one set of histories
- node.old_nodes.find(:all, :order => 'timestamp asc').each do |old_node|
- sequence_id = 1 #sequence_id refers to the sequence of node tags within a history
- old_node.tags.split(';').each do |tag|
- ont = OldNodeTag.new
- ont.id = node.id #the id of the node tag
- ont.k = tag.split('=')[0] || ''
- ont.v = tag.split('=')[1] || ''
- ont.version = version
- ont.sequence_id = sequence_id
- ont.save! || raise
- sequence_id += 1
- end
- version += 1
- end
- end
- offset += limit
- end
- end
-end
--- /dev/null
+t1:
+ id: 1
+ k: testvisible
+ v: yes
+
+t2:
+ id: 2
+ k: testused
+ v: yes
+
+t3:
+ id: 3
+ k: test
+ v: yes
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+<% SCALE = 10000000 unless defined?(SCALE) %>
+
visible_node:
id: 1
- latitude: 1
- longitude: 1
+ latitude: <%= 1*SCALE %>
+ longitude: <%= 1*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(1,1) %>
timestamp: 2007-01-01 00:00:00
invisible_node:
id: 2
- latitude: 2
- longitude: 2
+ latitude: <%= 2*SCALE %>
+ longitude: <%= 2*SCALE %>
user_id: 1
visible: 0
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(2,2) %>
timestamp: 2007-01-01 00:00:00
used_node_1:
id: 3
- latitude: 3
- longitude: 3
+ latitude: <%= 3*SCALE %>
+ longitude: <%= 3*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(3,3) %>
timestamp: 2007-01-01 00:00:00
used_node_2:
id: 4
- latitude: 4
- longitude: 4
+ latitude: <%= 4*SCALE %>
+ longitude: <%= 4*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(4,4) %>
timestamp: 2007-01-01 00:00:00
node_used_by_relationship:
id: 5
- latitude: 5
- longitude: 5
+ latitude: <%= 5*SCALE %>
+ longitude: <%= 5*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(5,5) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_too_far_north:
+ id: 6
+ latitude: <%= 90.01*SCALE %>
+ longitude: <%= 6*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(90.01,6) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_north_limit:
+ id: 11
+ latitude: <%= 90*SCALE %>
+ longitude: <%= 11*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(90,11) %>
+ timestamp: 2008-07-08 14:50:00
+
+node_too_far_south:
+ id: 7
+ latitude: <%= -90.01*SCALE %>
+ longitude: <%= 7*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(-90.01,7) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_south_limit:
+ id: 12
+ latitude: <%= -90*SCALE %>
+ longitude: <%= 12*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(-90,12) %>
+ timestamp: 2008-07-08 15:02:18
+
+node_too_far_west:
+ id: 8
+ latitude: <%= 8*SCALE %>
+ longitude: <%= -180.01*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(8,-180.01) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_west_limit:
+ id: 13
+ latitude: <%= 13*SCALE %>
+ longitude: <%= -180*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(13,-180) %>
+ timestamp: 2008-07-08 15:17:37
+
+node_too_far_east:
+ id: 9
+ latitude: <%= 9*SCALE %>
+ longitude: <%= 180.01*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(9,180.01) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_east_limit:
+ id: 14
+ latitude: <%= 14*SCALE %>
+ longitude: <%= 180*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(14,180) %>
+ timestamp: 2008-07-08 15:46:16
+
+node_totally_wrong:
+ id: 10
+ latitude: <%= 200*SCALE %>
+ longitude: <%= 200*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(200,200) %>
timestamp: 2007-01-01 00:00:00
+
user_id: 1
timestamp: 2007-01-01 00:00:00
visible: 1
+ version: 1
invisible_way:
id: 2
user_id: 1
timestamp: 2007-01-01 00:00:00
visible: 0
+ version: 1
used_way:
id: 3
user_id: 1
timestamp: 2007-01-01 00:00:00
visible: 1
+ version: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
- id: 1
+ from_user_id: 1
+ title: test message 1
+ body: some body text
+ sent_on: "2008-05-01 12:34:56"
+ message_read: false
+ to_user_id: 2
+
two:
- id: 2
+ from_user_id: 2
+ title: test message 2
+ body: some body test
+ sent_on: "2008-05-02 12:45:23"
+ message_read: true
+ to_user_id: 1
--- /dev/null
+t1:
+ id: visible_node
+ k: testvisible
+ v: yes
+ version: 1
+
+t2:
+ id: used_node_1
+ k: testused
+ v: yes
+ version: 1
+
+t3:
+ id: used_node_2
+ k: test
+ v: yes
+ version: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+<% SCALE = 10000000 unless defined?(SCALE) %>
+
visible_node:
id: 1
- latitude: 1
- longitude: 1
+ latitude: <%= 1*SCALE %>
+ longitude: <%= 1*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(1,1) %>
timestamp: 2007-01-01 00:00:00
invisible_node:
id: 2
- latitude: 2
- longitude: 2
+ latitude: <%= 2*SCALE %>
+ longitude: <%= 2*SCALE %>
user_id: 1
visible: 0
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(2,2) %>
timestamp: 2007-01-01 00:00:00
used_node_1:
id: 3
- latitude: 3
- longitude: 3
+ latitude: <%= 3*SCALE %>
+ longitude: <%= 3*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(3,3) %>
timestamp: 2007-01-01 00:00:00
used_node_2:
id: 4
- latitude: 4
- longitude: 4
+ latitude: <%= 4*SCALE %>
+ longitude: <%= 4*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(4,4) %>
timestamp: 2007-01-01 00:00:00
node_used_by_relationship:
id: 5
- latitude: 5
- longitude: 5
+ latitude: <%= 5*SCALE %>
+ longitude: <%= 5*SCALE %>
user_id: 1
visible: 1
- tags: test=yes
+ version: 1
+ tile: <%= QuadTile.tile_for_point(5,5) %>
timestamp: 2007-01-01 00:00:00
+node_too_far_north:
+ id: 6
+ latitude: <%= 90.01*SCALE %>
+ longitude: <%= 6*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(90.01,6) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_north_limit:
+ id: 11
+ latitude: <%= 90*SCALE %>
+ longitude: <%= 11*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(90,11) %>
+ timestamp: 2008-07-08 14:50:00
+
+node_too_far_south:
+ id: 7
+ latitude: <%= -90.01*SCALE %>
+ longitude: <%= 7*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(-90.01,7) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_south_limit:
+ id: 12
+ latitude: <%= -90*SCALE %>
+ longitude: <%= 12*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(-90,12) %>
+ timestamp: 2008-07-08 15:02:18
+
+node_too_far_west:
+ id: 8
+ latitude: <%= 8*SCALE %>
+ longitude: <%= -180.01*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(8,-180.01) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_west_limit:
+ id: 13
+ latitude: <%= 13*SCALE %>
+ longitude: <%= -180*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(13,-180) %>
+ timestamp: 2008-07-08 15:17:37
+
+node_too_far_east:
+ id: 9
+ latitude: <%= 9*SCALE %>
+ longitude: <%= 180.01*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(9,180.01) %>
+ timestamp: 2007-01-01 00:00:00
+
+node_east_limit:
+ id: 14
+ latitude: <%= 14*SCALE %>
+ longitude: <%= 180*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(14,180) %>
+ timestamp: 2008-07-08 15:46:16
+
+node_totally_wrong:
+ id: 10
+ latitude: <%= 200*SCALE %>
+ longitude: <%= 200*SCALE %>
+ user_id: 1
+ visible: 1
+ version: 1
+ tile: <%= QuadTile.tile_for_point(200,200) %>
+ timestamp: 2007-01-01 00:00:00
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
-# one:
-# column: value
-#
-# two:
-# column: value
+a:
+ user_id: 1
+ k: "key"
+ v: "value"
+
+two:
+ user_id: 1
+ k: "some_key"
+ v: "some_value"
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
normal_user:
- email: test@openstreetmap.org
id: 1
+ email: test@openstreetmap.org
active: 1
pass_crypt: <%= Digest::MD5.hexdigest('test') %>
creation_time: "2007-01-01 00:00:00"
display_name: test
data_public: 0
description: test
- home_lat: 1
- home_lon: 1
+ home_lat: 12.1
+ home_lon: 12.1
home_zoom: 3
+
+second_user:
+ id: 2
+ email: test@example.com
+ active: 1
+ pass_crypt: <%= Digest::MD5.hexdigest('test') %>
+ creation_time: "2008-05-01 01:23:45"
+ display_name: test2
+ data_public: 1
+ description: some test description
+ home_lat: 12
+ home_lon: 12
+ home_zoom: 12
+
+inactive_user:
+ id: 3
+ email: inactive@openstreetmap.org
+ active: 0
+ pass_crypt: <%= Digest::MD5::hexdigest('test2') %>
+ display_name: Inactive User
+ data_public: 1
+ description: description
+ home_lat: 123.4
+ home_lon: 12.34
+ home_zoom: 15
-t1:
+t1a:
id: 1
node_id: 3
sequence_id: 1
version: 1
+t1b:
+ id: 1
+ node_id: 4
+ sequence_id: 2
+ version: 1
+
t2:
id: 2
node_id: 3
def test_map
node = current_nodes(:used_node_1)
- bbox = "#{node.latitude-0.1},#{node.longitude-0.1},#{node.latitude+0.1},#{node.longitude+0.1}"
+ bbox = "#{node.lat-0.1},#{node.lon-0.1},#{node.lat+0.1},#{node.lon+0.1}"
get :map, :bbox => bbox
if $VERBOSE
print @response.body
assert_response :unauthorized
# now set auth
- basic_authorization("test@openstreetmap.org", "test");
+ basic_authorization(users(:normal_user).email, "test");
# this should work
delete :delete, :id => current_nodes(:visible_node).id
# Test reading old ways.
# -------------------------------------
- def test_history
+ def test_history_visible
# check that a visible way is returned properly
get :history, :id => ways(:visible_way).id
assert_response :success
-
+ end
+
+ def test_history_invisible
# check chat a non-existent way is not returned
get :history, :id => 0
assert_response :not_found
get :ways_for_node, :id => current_nodes(:used_node_1).id
assert_response :success
# FIXME check whether this contains the stuff we want!
- print @response.body
+ #print @response.body
+ # Needs to be updated when changing fixtures
+ # The generator should probably be defined in the environment.rb file
+ # in the same place as the api version
+ assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
+ assert_select "osm way", 3
+ assert_select "osm way nd", 3
+ assert_select "osm way tag", 3
# check the "full" mode
get :full, :id => current_ways(:visible_way).id
assert_response :success
# FIXME check whether this contains the stuff we want!
- print @response.body
+ #print @response.body
+ # Check the way is correctly returned
+ way = current_ways(:visible_way)
+ assert_select "osm way[id=#{way.id}][version=#{way.version}][visible=#{way.visible}]", 1
+ assert_select "osm way nd[ref=#{way.way_nodes[0].node_id}]", 1
+ # Check that the node is correctly returned
+ nd = current_ways(:visible_way).nodes
+ assert_equal 1, nd.count
+ nda = nd[0]
+ assert_select "osm node[id=#{nda.id}][version=#{nda.version}][lat=#{nda.lat}][lon=#{nda.lon}]", 1
end
# -------------------------------------
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
+load 'composite_primary_keys/fixtures.rb'
class Test::Unit::TestCase
# Transactional fixtures accelerate your tests by wrapping each test method
def self.api_fixtures
fixtures :users
- fixtures :current_nodes, :nodes
+ fixtures :current_nodes, :nodes, :current_node_tags
set_fixture_class :current_nodes => :Node
set_fixture_class :nodes => :OldNode
+ set_fixture_class :current_node_tags => :NodeTag
fixtures :current_ways, :current_way_nodes, :current_way_tags
set_fixture_class :current_ways => :Way
--- /dev/null
+require File.dirname(__FILE__) + '/../test_helper'
+
+class CurrentNodeTagTest < Test::Unit::TestCase
+ fixtures :current_node_tags, :current_nodes
+ set_fixture_class :current_nodes => :Node
+ set_fixture_class :current_node_tags => :NodeTag
+
+ def test_tag_count
+ assert_equal 3, NodeTag.count
+ node_tag_count(:visible_node, 1)
+ node_tag_count(:invisible_node, 1)
+ node_tag_count(:used_node_1, 1)
+ end
+
+ def node_tag_count (node, count)
+ nod = current_nodes(node)
+ assert_equal count, nod.node_tags.count
+ end
+
+end
require File.dirname(__FILE__) + '/../test_helper'
class MessageTest < Test::Unit::TestCase
- fixtures :messages
+ fixtures :messages, :users
- # Replace this with your real tests.
- def test_truth
- assert true
+ # This needs to be updated when new fixtures are added
+ # or removed.
+ def test_check_message_count
+ assert_equal 2, Message.count
+ end
+
+ def test_check_empty_message_fails
+ message = Message.new
+ assert !message.valid?
+ assert message.errors.invalid?(:title)
+ assert message.errors.invalid?(:body)
+ assert message.errors.invalid?(:sent_on)
+ assert true, message.message_read
+ end
+
+ def test_validating_msgs
+ message = messages(:one)
+ assert message.valid?
+ massage = messages(:two)
+ assert message.valid?
+ end
+
+ def test_invalid_send_recipient
+ message = messages(:one)
+ message.sender = nil
+ message.recipient = nil
+ assert !message.valid?
end
end
require File.dirname(__FILE__) + '/../test_helper'
class NodeTest < Test::Unit::TestCase
- fixtures :current_nodes, :nodes, :users
+ fixtures :current_nodes, :users, :current_node_tags, :nodes, :node_tags
set_fixture_class :current_nodes => :Node
set_fixture_class :nodes => :OldNode
-
+ set_fixture_class :node_tags => :OldNodeTag
+ set_fixture_class :current_node_tags => :NodeTag
+
+ def test_node_too_far_north
+ invalid_node_test(:node_too_far_north)
+ end
+
+ def test_node_north_limit
+ valid_node_test(:node_north_limit)
+ end
+
+ def test_node_too_far_south
+ invalid_node_test(:node_too_far_south)
+ end
+
+ def test_node_south_limit
+ valid_node_test(:node_south_limit)
+ end
+
+ def test_node_too_far_west
+ invalid_node_test(:node_too_far_west)
+ end
+
+ def test_node_west_limit
+ valid_node_test(:node_west_limit)
+ end
+
+ def test_node_too_far_east
+ invalid_node_test(:node_too_far_east)
+ end
+
+ def test_node_east_limit
+ valid_node_test(:node_east_limit)
+ end
+
+ def test_totally_wrong
+ invalid_node_test(:node_totally_wrong)
+ end
+
+ # This helper method will check to make sure that a node is within the world, and
+ # has the the same lat, lon and timestamp than what was put into the db by
+ # the fixture
+ def valid_node_test(nod)
+ node = current_nodes(nod)
+ dbnode = Node.find(node.id)
+ assert_equal dbnode.lat, node.latitude.to_f/SCALE
+ assert_equal dbnode.lon, node.longitude.to_f/SCALE
+ assert_equal dbnode.user_id, node.user_id
+ assert_equal dbnode.timestamp, node.timestamp
+ assert_equal dbnode.version, node.version
+ assert_equal dbnode.visible, node.visible
+ #assert_equal node.tile, QuadTile.tile_for_point(node.lat, node.lon)
+ assert_valid node
+ end
+
+ # This helper method will check to make sure that a node is outwith the world,
+ # and has the same lat, lon and timesamp than what was put into the db by the
+ # fixture
+ def invalid_node_test(nod)
+ node = current_nodes(nod)
+ dbnode = Node.find(node.id)
+ assert_equal dbnode.lat, node.latitude.to_f/SCALE
+ assert_equal dbnode.lon, node.longitude.to_f/SCALE
+ assert_equal dbnode.user_id, node.user_id
+ assert_equal dbnode.timestamp, node.timestamp
+ assert_equal dbnode.version, node.version
+ assert_equal dbnode.visible, node.visible
+ #assert_equal node.tile, QuadTile.tile_for_point(node.lat, node.lon)
+ assert_equal false, dbnode.valid?
+ end
+
+ # Check that you can create a node and store it
def test_create
node_template = Node.new(:latitude => 12.3456,
:longitude => 65.4321,
- :user_id => users(:normal_user).id,
- :visible => 1,
- :tags => "")
+ :user_id => users(:normal_user),
+ :visible => 1,
+ :version => 1)
assert node_template.save_with_history!
node = Node.find(node_template.id)
assert_equal node_template.longitude, node.longitude
assert_equal node_template.user_id, node.user_id
assert_equal node_template.visible, node.visible
- assert_equal node_template.tags, node.tags
assert_equal node_template.timestamp.to_i, node.timestamp.to_i
assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1
end
def test_update
- node_template = Node.find(1)
+ node_template = Node.find(current_nodes(:visible_node).id)
assert_not_nil node_template
assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1
node_template.latitude = 12.3456
node_template.longitude = 65.4321
- node_template.tags = "updated=yes"
+ #node_template.tags = "updated=yes"
assert node_template.save_with_history!
node = Node.find(node_template.id)
assert_equal node_template.longitude, node.longitude
assert_equal node_template.user_id, node.user_id
assert_equal node_template.visible, node.visible
- assert_equal node_template.tags, node.tags
+ #assert_equal node_template.tags, node.tags
assert_equal node_template.timestamp.to_i, node.timestamp.to_i
assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 2
assert_equal node_template.longitude, old_node.longitude
assert_equal node_template.user_id, old_node.user_id
assert_equal node_template.visible, old_node.visible
- assert_equal node_template.tags, old_node.tags
+ #assert_equal node_template.tags, old_node.tags
assert_equal node_template.timestamp.to_i, old_node.timestamp.to_i
end
def test_delete
- node_template = Node.find(1)
+ node_template = Node.find(current_nodes(:visible_node))
assert_not_nil node_template
assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 1
assert_equal node_template.longitude, node.longitude
assert_equal node_template.user_id, node.user_id
assert_equal node_template.visible, node.visible
- assert_equal node_template.tags, node.tags
+ #assert_equal node_template.tags, node.tags
assert_equal node_template.timestamp.to_i, node.timestamp.to_i
assert_equal OldNode.find(:all, :conditions => [ "id = ?", node_template.id ]).length, 2
assert_equal node_template.longitude, old_node.longitude
assert_equal node_template.user_id, old_node.user_id
assert_equal node_template.visible, old_node.visible
- assert_equal node_template.tags, old_node.tags
+ #assert_equal node_template.tags, old_node.tags
assert_equal node_template.timestamp.to_i, old_node.timestamp.to_i
end
end
--- /dev/null
+require File.dirname(__FILE__) + '/../test_helper'
+
+class OldNodeTest < Test::Unit::TestCase
+ fixtures :current_nodes, :users, :current_node_tags, :nodes, :node_tags
+ set_fixture_class :current_nodes => :Node
+ set_fixture_class :nodes => :OldNode
+ set_fixture_class :node_tags => :OldNodeTag
+ set_fixture_class :current_node_tags => :NodeTag
+
+ def test_node_too_far_north
+ invalid_node_test(:node_too_far_north)
+ end
+
+ def test_node_north_limit
+ valid_node_test(:node_north_limit)
+ end
+
+ def test_node_too_far_south
+ invalid_node_test(:node_too_far_south)
+ end
+
+ def test_node_south_limit
+ valid_node_test(:node_south_limit)
+ end
+
+ def test_node_too_far_west
+ invalid_node_test(:node_too_far_west)
+ end
+
+ def test_node_west_limit
+ valid_node_test(:node_west_limit)
+ end
+
+ def test_node_too_far_east
+ invalid_node_test(:node_too_far_east)
+ end
+
+ def test_node_east_limit
+ valid_node_test(:node_east_limit)
+ end
+
+ def test_totally_wrong
+ invalid_node_test(:node_totally_wrong)
+ end
+
+ # This helper method will check to make sure that a node is within the world, and
+ # has the the same lat, lon and timestamp than what was put into the db by
+ # the fixture
+ def valid_node_test(nod)
+ node = nodes(nod)
+ dbnode = Node.find(node.id)
+ assert_equal dbnode.lat, node.latitude.to_f/SCALE
+ assert_equal dbnode.lon, node.longitude.to_f/SCALE
+ assert_equal dbnode.user_id, node.user_id
+ assert_equal dbnode.version, node.version
+ assert_equal dbnode.visible, node.visible
+ assert_equal dbnode.timestamp, node.timestamp
+ #assert_equal node.tile, QuadTile.tile_for_point(nodes(nod).lat, nodes(nod).lon)
+ assert_valid node
+ end
+
+ # This helpermethod will check to make sure that a node is outwith the world,
+ # and has the same lat, lon and timesamp than what was put into the db by the
+ # fixture
+ def invalid_node_test(nod)
+ node = nodes(nod)
+ dbnode = Node.find(node.id)
+ assert_equal dbnode.lat, node.latitude.to_f/SCALE
+ assert_equal dbnode.lon, node.longitude.to_f/SCALE
+ assert_equal dbnode.user_id, node.user_id
+ assert_equal dbnode.version, node.version
+ assert_equal dbnode.visible, node.visible
+ assert_equal dbnode.timestamp, node.timestamp
+ #assert_equal node.tile, QuadTile.tile_for_point(nodes(nod).lat, nodes(nod).lon)
+ assert_equal false, node.valid?
+ end
+
+
+end
require File.dirname(__FILE__) + '/../test_helper'
class UserPreferenceTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- def test_truth
- assert true
+ fixtures :users, :user_preferences
+
+ # This checks to make sure that there are two user preferences
+ # stored in the test database.
+ # This test needs to be updated for every addition/deletion from
+ # the fixture file
+ def test_check_count
+ assert_equal 2, UserPreference.count
+ end
+
+ # Checks that you cannot add a new preference, that is a duplicate
+ def test_add_duplicate_preference
+ up = user_preferences(:a)
+ newUP = UserPreference.new
+ newUP.user = users(:normal_user)
+ newUP.k = up.k
+ newUP.v = "some other value"
+ assert_not_equal newUP.v, up.v
+ assert_raise (ActiveRecord::StatementInvalid) {newUP.save}
end
+
+
end
class UserTest < Test::Unit::TestCase
fixtures :users
-
- # Replace this with your real tests.
- def test_truth
- assert true
+
+ def test_invalid_with_empty_attributes
+ user = User.new
+ assert !user.valid?
+ assert user.errors.invalid?(:email)
+ assert user.errors.invalid?(:pass_crypt)
+ assert user.errors.invalid?(:display_name)
+ assert user.errors.invalid?(:email)
+ assert !user.errors.invalid?(:home_lat)
+ assert !user.errors.invalid?(:home_lon)
+ assert !user.errors.invalid?(:home_zoom)
+ end
+
+ def test_unique_email
+ new_user = User.new(:email => users(:normal_user).email,
+ :active => 1,
+ :pass_crypt => Digest::MD5.hexdigest('test'),
+ :display_name => "new user",
+ :data_public => 1,
+ :description => "desc")
+ assert !new_user.save
+ assert_equal ActiveRecord::Errors.default_error_messages[:taken], new_user.errors.on(:email)
+ end
+
+ def test_unique_display_name
+ new_user = User.new(:email => "tester@openstreetmap.org",
+ :active => 0,
+ :pass_crypt => Digest::MD5.hexdigest('test'),
+ :display_name => users(:normal_user).display_name,
+ :data_public => 1,
+ :description => "desc")
+ assert !new_user.save
+ assert_equal ActiveRecord::Errors.default_error_messages[:taken], new_user.errors.on(:display_name)
+ end
+
+ def test_email_valid
+ ok = %w{ a@s.com test@shaunmcdonald.me.uk hello_local@ping-d.ng
+ test_local@openstreetmap.org test-local@example.com
+ 輕觸搖晃的遊戲@ah.com も対応します@s.name }
+ bad = %w{ hi ht@ n@ @.com help@.me.uk help"hi.me.uk も対@応します }
+
+ ok.each do |name|
+ user = users(:normal_user)
+ user.email = name
+ assert user.valid?, user.errors.full_messages
+ end
+
+ bad.each do |name|
+ user = users(:normal_user)
+ user.email = name
+ assert !user.valid?, "#{name} is valid when it shouldn't be"
+ end
+ end
+
+ def test_display_name_length
+ user = users(:normal_user)
+ user.display_name = "123"
+ assert user.valid?, " should allow nil display name"
+ user.display_name = "12"
+ assert !user.valid?, "should not allow 2 char name"
+ user.display_name = ""
+ assert !user.valid?
+ user.display_name = nil
+ # Don't understand why it isn't allowing a nil value,
+ # when the validates statements specifically allow it
+ # It appears the database does not allow null values
+ assert !user.valid?
+ end
+
+ def test_display_name_valid
+ # Due to sanitisation in the view some of these that you might not
+ # expact are allowed
+ # However, would they affect the xml planet dumps?
+ ok = [ "Name", "'me", "he\"", "#ping", "<hr>", "*ho", "\"help\"@",
+ "vergrößern", "ルシステムにも対応します", "輕觸搖晃的遊戲" ]
+ # These need to be 3 chars in length, otherwise the length test above
+ # should be used.
+ bad = [ "<hr/>", "test@example.com", "s/f", "aa/", "aa;", "aa.",
+ "aa,", "aa?", "/;.,?", "も対応します/" ]
+ ok.each do |display_name|
+ user = users(:normal_user)
+ user.display_name = display_name
+ assert user.valid?, "#{display_name} is invalid, when it should be"
+ end
+
+ bad.each do |display_name|
+ user = users(:normal_user)
+ user.display_name = display_name
+ assert !user.valid?, "#{display_name} is valid when it shouldn't be"
+ assert_equal "is invalid", user.errors.on(:display_name)
+ end
+ end
+
+ def test_friend_with
+ assert_equal false, users(:normal_user).is_friends_with?(users(:second_user))
+ assert_equal false, users(:normal_user).is_friends_with?(users(:inactive_user))
+ assert_equal false, users(:second_user).is_friends_with?(users(:normal_user))
+ assert_equal false, users(:second_user).is_friends_with?(users(:inactive_user))
+ assert_equal false, users(:inactive_user).is_friends_with?(users(:normal_user))
+ assert_equal false, users(:inactive_user).is_friends_with?(users(:second_user))
+ end
+
+ def test_users_nearby
+ # second user has their data public and is close by normal user
+ assert_equal [users(:second_user)], users(:normal_user).nearby
+ # second_user has normal user nearby, but normal user has their data private
+ assert_equal [], users(:second_user).nearby
+ # inactive_user has no user nearby
+ assert_equal [], users(:inactive_user).nearby
+ end
+
+ def test_friends_with
+ # make normal user a friend of second user
+ # it should be a one way friend accossitation
+ assert_equal 0, Friend.count
+ norm = users(:normal_user)
+ sec = users(:second_user)
+ friend = Friend.new
+ friend.user = norm
+ friend.friend_user_id = sec.id
+ friend.save
+ assert_equal [sec], norm.nearby
+ assert_equal 1, norm.nearby.size
+ assert_equal 1, Friend.count
+ assert_equal true, norm.is_friends_with?(sec)
+ assert_equal false, sec.is_friends_with?(norm)
+ assert_equal false, users(:normal_user).is_friends_with?(users(:inactive_user))
+ assert_equal false, users(:second_user).is_friends_with?(users(:normal_user))
+ assert_equal false, users(:second_user).is_friends_with?(users(:inactive_user))
+ assert_equal false, users(:inactive_user).is_friends_with?(users(:normal_user))
+ assert_equal false, users(:inactive_user).is_friends_with?(users(:second_user))
+ Friend.delete(friend)
+ assert_equal 0, Friend.count
end
end