]> git.openstreetmap.org Git - rails.git/blob - app/models/relation.rb
api06: simplify exception handling and add exception handling to the diff
[rails.git] / app / models / relation.rb
1 class Relation < ActiveRecord::Base
2   require 'xml/libxml'
3   
4   belongs_to :user
5
6   has_many :relation_members, :foreign_key => 'id'
7   has_many :relation_tags, :foreign_key => 'id'
8
9   has_many :old_relations, :foreign_key => 'id', :order => 'version'
10
11   set_table_name 'current_relations'
12
13   def self.from_xml(xml, create=false)
14     begin
15       p = XML::Parser.new
16       p.string = xml
17       doc = p.parse
18
19       doc.find('//osm/relation').each do |pt|
20         return Relation.from_xml_node(pt, create)
21       end
22     rescue
23       return nil
24     end
25   end
26
27   def self.from_xml_node(pt, create=false)
28     relation = Relation.new
29
30     if !create and pt['id'] != '0'
31       relation.id = pt['id'].to_i
32     end
33
34     relation.version = pt['version']
35
36     if create
37       relation.timestamp = Time.now
38       relation.visible = true
39     else
40       if pt['timestamp']
41         relation.timestamp = Time.parse(pt['timestamp'])
42       end
43     end
44
45     pt.find('tag').each do |tag|
46       relation.add_tag_keyval(tag['k'], tag['v'])
47     end
48
49     pt.find('member').each do |member|
50       relation.add_member(member['type'], member['ref'], member['role'])
51     end
52
53     return relation
54   end
55
56   def to_xml
57     doc = OSM::API.new.get_xml_doc
58     doc.root << to_xml_node()
59     return doc
60   end
61
62   def to_xml_node(user_display_name_cache = nil)
63     el1 = XML::Node.new 'relation'
64     el1['id'] = self.id.to_s
65     el1['visible'] = self.visible.to_s
66     el1['timestamp'] = self.timestamp.xmlschema
67     el1['version'] = self.version.to_s
68
69     user_display_name_cache = {} if user_display_name_cache.nil?
70     
71     if user_display_name_cache and user_display_name_cache.key?(self.user_id)
72       # use the cache if available
73     elsif self.user.data_public?
74       user_display_name_cache[self.user_id] = self.user.display_name
75     else
76       user_display_name_cache[self.user_id] = nil
77     end
78
79     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
80
81     self.relation_members.each do |member|
82       p=0
83       #if visible_members
84       #  # if there is a list of visible members then use that to weed out deleted segments
85       #  if visible_members[member.member_type][member.member_id]
86       #    p=1
87       #  end
88       #else
89         # otherwise, manually go to the db to check things
90         if member.member.visible?
91           p=1
92         end
93       #end
94       if p
95         e = XML::Node.new 'member'
96         e['type'] = member.member_type
97         e['ref'] = member.member_id.to_s 
98         e['role'] = member.member_role
99         el1 << e
100        end
101     end
102
103     self.relation_tags.each do |tag|
104       e = XML::Node.new 'tag'
105       e['k'] = tag.k
106       e['v'] = tag.v
107       el1 << e
108     end
109     return el1
110   end 
111
112     
113   # collect relationships. currently done in one big block at the end;
114   # may need to move this upwards if people want automatic completion of
115   # relationships, i.e. deliver referenced objects like we do with ways... 
116   # FIXME: rip out the fucking SQL
117   def self.find_for_nodes_and_ways(node_ids, way_ids)
118     relations = []
119
120     if node_ids.length > 0
121       relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
122             "e.visible=1 and " +
123             "em.id = e.id and em.member_type='node' and em.member_id in (#{node_ids.join(',')})")
124     end
125     if way_ids.length > 0
126       relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
127             "e.visible=1 and " +
128             "em.id = e.id and em.member_type='way' and em.member_id in (#{way_ids.join(',')})")
129     end
130
131     relations # if you don't do this then it returns nil and not []
132   end
133
134
135   # FIXME is this really needed?
136   def members
137     unless @members
138       @members = Array.new
139       self.relation_members.each do |member|
140         @members += [[member.member_type,member.member_id,member.member_role]]
141       end
142     end
143     @members
144   end
145
146   def tags
147     unless @tags
148       @tags = Hash.new
149       self.relation_tags.each do |tag|
150         @tags[tag.k] = tag.v
151       end
152     end
153     @tags
154   end
155
156   def members=(m)
157     @members = m
158   end
159
160   def tags=(t)
161     @tags = t
162   end
163
164   def add_member(type,id,role)
165     @members = Array.new unless @members
166     @members += [[type,id,role]]
167   end
168
169   def add_tag_keyval(k, v)
170     @tags = Hash.new unless @tags
171     @tags[k] = v
172   end
173
174   def save_with_history!
175     Relation.transaction do
176       t = Time.now
177       self.version += 1
178       self.timestamp = t
179       self.save!
180
181       tags = self.tags
182       RelationTag.delete_all(['id = ?', self.id])
183       tags.each do |k,v|
184         tag = RelationTag.new
185         tag.k = k
186         tag.v = v
187         tag.id = self.id
188         tag.save!
189       end
190
191       members = self.members
192       RelationMember.delete_all(['id = ?', self.id])
193       members.each do |n|
194         mem = RelationMember.new
195         mem.id = self.id
196         mem.member_type = n[0];
197         mem.member_id = n[1];
198         mem.member_role = n[2];
199         mem.save!
200       end
201
202       old_relation = OldRelation.from_relation(self)
203       old_relation.timestamp = t
204       old_relation.save_with_dependencies!
205     end
206   end
207
208   def delete_with_history(user)
209     if self.visible
210       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 ])
211         raise OSM::APIPreconditionFailedError.new
212       else
213         self.user_id = user.id
214         self.tags = []
215         self.members = []
216         self.visible = false
217         save_with_history!
218       end
219     else
220       raise OSM::APIAlreadyDeletedError.new
221     end
222   end
223
224   def update_from(new_relation, user)
225     if !new_relation.preconditions_ok?
226       raise OSM::APIPreconditionFailedError.new
227     elsif new_relation.version != version
228       raise OSM::APIVersionMismatchError.new(new_relation.version, version)
229     else
230       self.user_id = user.id
231       self.tags = new_relation.tags
232       self.members = new_relation.members
233       self.visible = true
234       save_with_history!
235     end
236   end
237
238   def preconditions_ok?
239     self.members.each do |m|
240       if (m[0] == "node")
241         n = Node.find(:first, :conditions => ["id = ?", m[1]])
242         unless n and n.visible 
243           return false
244         end
245       elsif (m[0] == "way")
246         w = Way.find(:first, :conditions => ["id = ?", m[1]])
247         unless w and w.visible and w.preconditions_ok?
248           return false
249         end
250       elsif (m[0] == "relation")
251         e = Relation.find(:first, :conditions => ["id = ?", m[1]])
252         unless e and e.visible and e.preconditions_ok?
253           return false
254         end
255       else
256         return false
257       end
258     end
259     return true
260   rescue
261     return false
262   end
263
264 end