changing the belongs_to and has_many items in the models to reflect the new foriegn...
[rails.git] / app / models / relation.rb
1 class Relation < ActiveRecord::Base
2   require 'xml/libxml'
3   
4   set_table_name 'current_relations'
5
6   belongs_to :changeset
7
8   has_many :old_relations, :foreign_key => 'id', :order => 'version'
9
10   has_many :relation_members, :foreign_key => 'id'
11   has_many :relation_tags, :foreign_key => 'id'
12
13   has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
14   has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
15
16   def self.from_xml(xml, create=false)
17     begin
18       p = XML::Parser.new
19       p.string = xml
20       doc = p.parse
21
22       doc.find('//osm/relation').each do |pt|
23         return Relation.from_xml_node(pt, create)
24       end
25     rescue
26       return nil
27     end
28   end
29
30   def self.from_xml_node(pt, create=false)
31     relation = Relation.new
32
33     if !create and pt['id'] != '0'
34       relation.id = pt['id'].to_i
35     end
36
37     relation.version = pt['version']
38
39     if create
40       relation.timestamp = Time.now
41       relation.visible = true
42     else
43       if pt['timestamp']
44         relation.timestamp = Time.parse(pt['timestamp'])
45       end
46     end
47
48     pt.find('tag').each do |tag|
49       relation.add_tag_keyval(tag['k'], tag['v'])
50     end
51
52     pt.find('member').each do |member|
53       relation.add_member(member['type'], member['ref'], member['role'])
54     end
55
56     return relation
57   end
58
59   def to_xml
60     doc = OSM::API.new.get_xml_doc
61     doc.root << to_xml_node()
62     return doc
63   end
64
65   def to_xml_node(user_display_name_cache = nil)
66     el1 = XML::Node.new 'relation'
67     el1['id'] = self.id.to_s
68     el1['visible'] = self.visible.to_s
69     el1['timestamp'] = self.timestamp.xmlschema
70     el1['version'] = self.version.to_s
71
72     user_display_name_cache = {} if user_display_name_cache.nil?
73     
74     if user_display_name_cache and user_display_name_cache.key?(self.user_id)
75       # use the cache if available
76     elsif self.user.data_public?
77       user_display_name_cache[self.user_id] = self.user.display_name
78     else
79       user_display_name_cache[self.user_id] = nil
80     end
81
82     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
83
84     self.relation_members.each do |member|
85       p=0
86       #if visible_members
87       #  # if there is a list of visible members then use that to weed out deleted segments
88       #  if visible_members[member.member_type][member.member_id]
89       #    p=1
90       #  end
91       #else
92         # otherwise, manually go to the db to check things
93         if member.member.visible?
94           p=1
95         end
96       #end
97       if p
98         e = XML::Node.new 'member'
99         e['type'] = member.member_type
100         e['ref'] = member.member_id.to_s 
101         e['role'] = member.member_role
102         el1 << e
103        end
104     end
105
106     self.relation_tags.each do |tag|
107       e = XML::Node.new 'tag'
108       e['k'] = tag.k
109       e['v'] = tag.v
110       el1 << e
111     end
112     return el1
113   end 
114
115   def self.find_for_nodes(ids, options = {})
116     if ids.empty?
117       return []
118     else
119       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'node' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
120         return self.find(:all, options)
121       end
122     end
123   end
124
125   def self.find_for_ways(ids, options = {})
126     if ids.empty?
127       return []
128     else
129       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'way' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
130         return self.find(:all, options)
131       end
132     end
133   end
134
135   def self.find_for_relations(ids, options = {})
136     if ids.empty?
137       return []
138     else
139       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'relation' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
140         return self.find(:all, options)
141       end
142     end
143   end
144
145   # FIXME is this really needed?
146   def members
147     unless @members
148       @members = Array.new
149       self.relation_members.each do |member|
150         @members += [[member.member_type,member.member_id,member.member_role]]
151       end
152     end
153     @members
154   end
155
156   def tags
157     unless @tags
158       @tags = Hash.new
159       self.relation_tags.each do |tag|
160         @tags[tag.k] = tag.v
161       end
162     end
163     @tags
164   end
165
166   def members=(m)
167     @members = m
168   end
169
170   def tags=(t)
171     @tags = t
172   end
173
174   def add_member(type,id,role)
175     @members = Array.new unless @members
176     @members += [[type,id,role]]
177   end
178
179   def add_tag_keyval(k, v)
180     @tags = Hash.new unless @tags
181     @tags[k] = v
182   end
183
184   def save_with_history!
185     Relation.transaction do
186       t = Time.now
187       self.version += 1
188       self.timestamp = t
189       self.save!
190
191       tags = self.tags
192       RelationTag.delete_all(['id = ?', self.id])
193       tags.each do |k,v|
194         tag = RelationTag.new
195         tag.k = k
196         tag.v = v
197         tag.id = self.id
198         tag.save!
199       end
200
201       members = self.members
202       RelationMember.delete_all(['id = ?', self.id])
203       members.each do |n|
204         mem = RelationMember.new
205         mem.id = self.id
206         mem.member_type = n[0];
207         mem.member_id = n[1];
208         mem.member_role = n[2];
209         mem.save!
210       end
211
212       old_relation = OldRelation.from_relation(self)
213       old_relation.timestamp = t
214       old_relation.save_with_dependencies!
215     end
216   end
217
218   def delete_with_history(user)
219     if self.visible
220       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 ])
221         raise OSM::APIPreconditionFailedError.new
222       else
223         self.user_id = user.id
224         self.tags = []
225         self.members = []
226         self.visible = false
227         save_with_history!
228       end
229     else
230       raise OSM::APIAlreadyDeletedError.new
231     end
232   end
233
234   def update_from(new_relation, user)
235     if !new_relation.preconditions_ok?
236       raise OSM::APIPreconditionFailedError.new
237     elsif new_relation.version != version
238       raise OSM::APIVersionMismatchError.new(new_relation.version, version)
239     else
240       self.user_id = user.id
241       self.tags = new_relation.tags
242       self.members = new_relation.members
243       self.visible = true
244       save_with_history!
245     end
246   end
247
248   def preconditions_ok?
249     # These are hastables that store an id in the index of all 
250     # the nodes/way/relations that have already been added.
251     # Once we know the id of the node/way/relation exists
252     # we check to see if it is already existing in the hashtable
253     # if it does, then we return false. Otherwise
254     # we add it to the relevant hash table, with the value true..
255     # Thus if you have nodes with the ids of 50 and 1 already in the
256     # relation, then the hash table nodes would contain:
257     # => {50=>true, 1=>true}
258     nodes = Hash.new
259     ways = Hash.new
260     relations = Hash.new
261     self.members.each do |m|
262       if (m[0] == "node")
263         n = Node.find(:first, :conditions => ["id = ?", m[1]])
264         unless n and n.visible 
265           return false
266         end
267         if nodes[m[1]]
268           return false
269         else
270           nodes[m[1]] = true
271         end
272       elsif (m[0] == "way")
273         w = Way.find(:first, :conditions => ["id = ?", m[1]])
274         unless w and w.visible and w.preconditions_ok?
275           return false
276         end
277         if ways[m[1]]
278           return false
279         else
280           ways[m[1]] = true
281         end
282       elsif (m[0] == "relation")
283         e = Relation.find(:first, :conditions => ["id = ?", m[1]])
284         unless e and e.visible and e.preconditions_ok?
285           return false
286         end
287         if relations[m[1]]
288           return false
289         else
290           relations[m[1]] = true
291         end
292       else
293         return false
294       end
295     end
296     return true
297   rescue
298     return false
299   end
300
301   # Temporary method to match interface to nodes
302   def tags_as_hash
303     return self.tags
304   end
305 end