Class: RelationProxy
- Includes:
- RubyLess
- Defined in:
- app/models/relation_proxy.rb
Overview
This is a mess and would need a rewrite…
Constant Summary collapse
- LINK_ATTRIBUTES =
Zena::Use::Relations::LINK_ATTRIBUTES
- LINK_ATTRIBUTES_SQL =
LINK_ATTRIBUTES.map {|sym| connection.quote_column_name(sym)}.join(',')
- LINK_SELECT =
"nodes.*,links.id AS link_id,#{LINK_ATTRIBUTES.map {|l| "links.#{l} AS l_#{l}"}.join(',')}"
Constants inherited from Relation
Instance Attribute Summary collapse
-
#add_links ⇒ Object
Returns the value of attribute add_links.
-
#last_target ⇒ Object
Returns the value of attribute last_target.
-
#link_errors ⇒ Object
Returns the value of attribute link_errors.
-
#other_link ⇒ Object
get.
-
#side ⇒ Object
Returns the value of attribute side.
-
#start ⇒ Object
Returns the value of attribute start.
Attributes inherited from Relation
Class Method Summary collapse
-
.find_by_role(role, source_kpath = nil) ⇒ Object
Find a role from a name.
-
.get_proxy(node, role) ⇒ Object
Find a relation proxy for a role through a given node.
Instance Method Summary collapse
- #as_unique? ⇒ Boolean
-
#attributes_to_update_valid? ⇒ Boolean
link can be changed if user can write in old and new 1.
-
#changed_link(link, attrs) ⇒ Object
Return updated link if changed or nil when nothing changed.
-
#link_side ⇒ Object
def source_unique self ? true : false end.
- #other_icon ⇒ Object
- #other_id ⇒ Object
-
#other_id=(v) ⇒ Object
set.
- #other_ids ⇒ Object
- #other_ids=(v) ⇒ Object
- #other_kpath ⇒ Object
-
#other_links ⇒ Object
find the links from the current context (source or target).
- #other_role ⇒ Object
- #other_side ⇒ Object
-
#other_vclass ⇒ Object
Get class of other element (used by QueryNode to properly set resulting class).
- #other_zip ⇒ Object
-
#other_zip=(zip_values) ⇒ Object
set.
- #other_zips ⇒ Object
- #other_zips=(v) ⇒ Object
-
#qb=(qb) ⇒ Object
set from query builder.
- #records(opts = {}) ⇒ Object
- #remove_link(link) ⇒ Object
-
#source=(start) ⇒ Object
Define the caller’s side.
-
#target=(start) ⇒ Object
Define the caller’s side.
- #this_kpath ⇒ Object
- #this_role ⇒ Object
- #unique? ⇒ Boolean
- #update_links! ⇒ Object
Methods inherited from Relation
#export, #source_role, #target_role
Instance Attribute Details
#add_links ⇒ Object
Returns the value of attribute add_links.
7 8 9 |
# File 'app/models/relation_proxy.rb', line 7 def add_links @add_links end |
#last_target ⇒ Object
Returns the value of attribute last_target.
7 8 9 |
# File 'app/models/relation_proxy.rb', line 7 def last_target @last_target end |
#link_errors ⇒ Object
Returns the value of attribute link_errors.
7 8 9 |
# File 'app/models/relation_proxy.rb', line 7 def link_errors @link_errors end |
#other_link ⇒ Object
get
72 73 74 |
# File 'app/models/relation_proxy.rb', line 72 def other_link @other_link end |
#side ⇒ Object
Returns the value of attribute side.
7 8 9 |
# File 'app/models/relation_proxy.rb', line 7 def side @side end |
#start ⇒ Object
Returns the value of attribute start.
7 8 9 |
# File 'app/models/relation_proxy.rb', line 7 def start @start end |
Class Method Details
.find_by_role(role, source_kpath = nil) ⇒ Object
Find a role from a name. If a source_kpath is provided, only roles that could be reached from this class are found.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'app/models/relation_proxy.rb', line 16 def find_by_role(role, source_kpath = nil) if source_kpath klasses = [] source_kpath.split(//).each_index { |i| klasses << source_kpath[0..i] } rel = find(:first, :conditions => ["((source_role = ? AND target_kpath IN (?)) OR (target_role = ? AND source_kpath IN (?))) AND site_id = ?", role, klasses, role, klasses, current_site[:id]]) else rel = find(:first, :conditions => ["(source_role = ? OR target_role = ?) AND site_id = ?", role, role, current_site[:id]]) end return nil unless rel if rel[:target_role] == role rel.side = :source else rel.side = :target end rel end |
.get_proxy(node, role) ⇒ Object
Find a relation proxy for a role through a given node. The finder makes sure the class path is compatible with the node’s class/virtual_class given as parameter.
38 39 40 41 42 43 44 45 46 47 |
# File 'app/models/relation_proxy.rb', line 38 def get_proxy(node, role) rel = find_by_role(role, node.new_record? ? nil : node.kpath) if rel && (node.new_record? || node.kpath =~ /\A#{rel.this_kpath}/) rel.start = node rel else # invalid relation for the given class path nil end end |
Instance Method Details
#as_unique? ⇒ Boolean
445 446 447 |
# File 'app/models/relation_proxy.rb', line 445 def as_unique? @side == :source ? source_unique : target_unique end |
#attributes_to_update_valid? ⇒ Boolean
link can be changed if user can write in old and new
-
can remove old link
-
can write in new target
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'app/models/relation_proxy.rb', line 250 def attributes_to_update_valid? return true unless @attributes_to_update || @links_to_delete @link_errors = {} @add_links = [] @del_links = [] @update_links = [] if @links_to_delete # only removing links @del_links = @links_to_delete @attributes_to_update = {} else # check if we have an update/create unless @attributes_to_update.has_key?(:id) # set during other_id= # try to find current id/ids if @other_link @attributes_to_update[:id] = @other_link[other_side] elsif link_id = @start.link_id @other_link = Link.find(link_id) @attributes_to_update[:id] = @other_link[other_side] @attributes_to_update[:link_id] = link_id elsif unique? if other_id @attributes_to_update[:id] = other_id elsif @attributes_to_update.keys == [:id] # ignore (set icon_id = nil when already == nil) else @link_errors['update'] = _('missing target') end else # error: cannot set other attributes (status/comment) on multiple nodes @link_errors['update'] = _('cannot update multiple targets') end end if @attributes_to_update[:id].kind_of?(Array) if unique? # TODO: translate @link_errors['arity'] = "Cannot set multiple targets on #{as_unique? ? 'one' : 'many'}-to-one relation '#{this_role}'." elsif (@attributes_to_update.keys & LINK_ATTRIBUTES) != [] keys = @attributes_to_update.keys keys.delete(:id) # TODO: translate @link_errors['arity'] = "Cannot set attributes #{keys.join(', ')} on multiple targets." end end return false if @link_errors != {} @link_errors = @attributes_to_update[:errors] || {} # 1. find what changed if @attributes_to_update[:id].kind_of?(Array) # ..-to-many # define all links # list of link ids set add_link_ids = @attributes_to_update[:id] # find all current links # TODO: this could be optimzed (avoid loading all links...) other_links.each do |link| obj_id = link[other_side] if add_link_ids.include?(obj_id) && (@attributes_to_update[:date].nil? || @attributes_to_update[:link_id] || @attributes_to_update[:date] == link[:date]) # ignore existing link add_link_ids.delete(obj_id) else # remove unused links / link to replace @del_links << link end end @add_links = add_link_ids.map {|obj_id| Hash[:id,obj_id] } elsif unique? # ..-to-one # define/update link if other_id == @attributes_to_update[:id] # same target: update @update_links << changed_link(other_link, @attributes_to_update) else # other target: replace @del_links = [other_link] if other_link @add_links << @attributes_to_update unless @attributes_to_update[:id].blank? end else # ..-to-many # add/update a link # TODO: optimize to avoid loading all links... if @attributes_to_update[:id].blank? && @attributes_to_update[:date] # delete @del_links = other_links.select {|l| @attributes_to_update[:date] == l[:date]} else links = other_links.select {|l| l[other_side] == @attributes_to_update[:id] && (@attributes_to_update[:date].nil? || @attributes_to_update[:link_id] || @attributes_to_update[:date] == l[:date])} if links != [] # update if (@attributes_to_update.keys & LINK_ATTRIBUTES) != [] links.each do |link| if link[other_side] == @attributes_to_update[:id] @update_links << changed_link(link, @attributes_to_update) end end end elsif @attributes_to_update[:id] == :ignore # bad id set, just used for error reporting else # add @add_links << @attributes_to_update end end end end id_to_zip = attributes_to_update[:id_to_zip] || {} # 2. can write in new target ? (and remove targets previous link) @add_links.each do |hash| # last_target is used by "linked_node" from Node to get hold of the last linked node if @last_target = find_target(hash[:id]) # store remote node so that we can use in index rebuild (scope_index) hash[:node] = @last_target # make sure we can overwrite previous link if as_unique if as_unique? if previous_link = Link.find(:first, :conditions => ["relation_id = ? AND #{other_side} = ?", self[:id], @last_target[:id]]) @del_links << previous_link end end else if zip = id_to_zip[hash[:id]] key = zip elsif node = secure(Node) { Node.find_by_id(hash[:id]) } key = node.zip else key = 'id' end @link_errors[key] = _('invalid target') end end # 1. can remove old link ? @del_links.each do |link| unless find_node(link[other_side], unique?) if zip = id_to_zip[link[other_side]] key = zip elsif node = secure(Node) { Node.find_by_id(hash[:id]) } key = node.zip else key = 'id' end @link_errors[key] = _('cannot remove link') end end @update_links.compact! return @link_errors == {} end |
#changed_link(link, attrs) ⇒ Object
Return updated link if changed or nil when nothing changed
409 410 411 412 413 414 415 416 417 418 419 |
# File 'app/models/relation_proxy.rb', line 409 def changed_link(link, attrs) changed = false LINK_ATTRIBUTES.each do |sym| next unless attrs.has_key?(sym) if attrs[sym] != link[sym] changed = true link[sym] = attrs[sym] end end changed ? link : nil end |
#link_side ⇒ Object
def source_unique
self[:source_unique] ? true : false
end
def target_unique
self[:target_unique] ? true : false
end
457 458 459 |
# File 'app/models/relation_proxy.rb', line 457 def link_side @side == :source ? 'source_id' : 'target_id' end |
#other_icon ⇒ Object
118 119 120 |
# File 'app/models/relation_proxy.rb', line 118 def other_icon @side == :source ? target_icon : source_icon end |
#other_id ⇒ Object
76 77 78 |
# File 'app/models/relation_proxy.rb', line 76 def other_id other_link ? other_link[other_side] : nil end |
#other_id=(v) ⇒ Object
set
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'app/models/relation_proxy.rb', line 145 def other_id=(v) attributes_to_update[:errors] = {} if !v.kind_of?(Array) && v.to_i < 0 # removing a link # TODO: support Array if link = other_links.select { |l| l[other_side] == -v }.first remove_link(link) else # ignore end elsif v.kind_of?(Array) if v.first.kind_of?(Node) node_by_id = attributes_to_update[:nodes] = {} v.each do |r| node_by_id[r.id.to_i] = r end attributes_to_update[:id] = v.map{|r| r.id.to_i} else attributes_to_update[:id] = v.select {|e| !e.blank?}.map(&:to_i) end else attributes_to_update[:id] = v.blank? ? nil : v.to_i end end |
#other_ids ⇒ Object
84 85 86 |
# File 'app/models/relation_proxy.rb', line 84 def other_ids (other_links || []).map { |l| l[other_side] } end |
#other_ids=(v) ⇒ Object
215 216 217 |
# File 'app/models/relation_proxy.rb', line 215 def other_ids=(v) self.other_id = v end |
#other_kpath ⇒ Object
238 239 240 |
# File 'app/models/relation_proxy.rb', line 238 def other_kpath @side == :source ? target_kpath : source_kpath end |
#other_links ⇒ Object
find the links from the current context (source or target)
243 244 245 |
# File 'app/models/relation_proxy.rb', line 243 def other_links @other_links ||= Link.find(:all, :conditions => ["relation_id = ? AND #{link_side} = ?", self[:id], @start[:id]]) end |
#other_role ⇒ Object
110 111 112 |
# File 'app/models/relation_proxy.rb', line 110 def other_role @side == :source ? self[:target_role] : self[:source_role] end |
#other_side ⇒ Object
461 462 463 |
# File 'app/models/relation_proxy.rb', line 461 def other_side @side == :source ? 'target_id' : 'source_id' end |
#other_vclass ⇒ Object
Get class of other element (used by QueryNode to properly set resulting class).
123 124 125 |
# File 'app/models/relation_proxy.rb', line 123 def other_vclass VirtualClass.find_by_kpath(@side == :source ? self[:target_kpath] : self[:source_kpath]) end |
#other_zip ⇒ Object
80 81 82 |
# File 'app/models/relation_proxy.rb', line 80 def other_zip other_zips ? other_zips.first : nil end |
#other_zip=(zip_values) ⇒ Object
set
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'app/models/relation_proxy.rb', line 171 def other_zip=(zip_values) # Translate ids and then set errors = attributes_to_update[:errors] = {} id_to_zip = attributes_to_update[:id_to_zip] = {} if zip_values.kind_of?(Array) attributes_to_update[:id] = [] zip_values.each do |zip| next if zip.blank? if id = secure(Node) { Node.translate_pseudo_id(zip, :id, @start) } # ok id_to_zip[id] = zip attributes_to_update[:id] << id else # error errors[zip] = _('could not be found') end end elsif zip_values.blank? # remove all attributes_to_update[:id] = nil else if id = secure(Node) { Node.translate_pseudo_id(zip_values, :id, @start) } if id < 0 # removing a link # TODO: support Array if link = other_links.select { |l| l[other_side] == -id }.first remove_link(link) else # ignore end else id_to_zip[id] = zip_values attributes_to_update[:id] = id end else # error # do not try to add attributes_to_update[:id] = :ignore errors[zip_values] = _('could not be found') end end end |
#other_zips ⇒ Object
88 89 90 91 92 |
# File 'app/models/relation_proxy.rb', line 88 def other_zips return nil unless @start[:id] return @other_zips if defined?(@other_zips) @other_zips = @records ? @records.map { |r| r.zip} : Zena::Db.fetch_ids("SELECT zip FROM nodes INNER JOIN links ON nodes.id=links.#{other_side} AND links.relation_id = #{self[:id]} AND links.#{link_side} = #{@start[:id]} WHERE #{secure_scope('nodes')} GROUP BY nodes.zip", 'zip') end |
#other_zips=(v) ⇒ Object
219 220 221 |
# File 'app/models/relation_proxy.rb', line 219 def other_zips=(v) self.other_zip = v end |
#qb=(qb) ⇒ Object
set from query builder
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/relation_proxy.rb', line 128 def qb=(qb) if qb.blank? self.other_id = [] else query = @start.class.build_query(:all, qb, :node_name => '@start', :main_class => @start.virtual_class, :rubyless_helper => @start.virtual_class, :default => {:order => 'id asc'} ) self.other_id = secure(Node) {Node.find_by_sql(eval(query.to_s))} || [] end rescue ::QueryBuilder::Error => err attributes_to_update[:errors] = {'base' => err.} end |
#records(opts = {}) ⇒ Object
94 95 96 97 98 99 100 101 102 |
# File 'app/models/relation_proxy.rb', line 94 def records(opts = {}) return nil unless @start[:id] return @records if defined?(@records) = { :select => "nodes.*, #{LINK_SELECT}", :joins => "INNER JOIN links ON nodes.id=links.#{other_side} AND links.relation_id = #{self[:id]} AND links.#{link_side} = #{@start[:id]}" }.merge(opts) @records = secure(Node) { Node.find(:all, ) } end |
#remove_link(link) ⇒ Object
223 224 225 226 |
# File 'app/models/relation_proxy.rb', line 223 def remove_link(link) @links_to_delete ||= [] @links_to_delete << link end |
#source=(start) ⇒ Object
Define the caller’s side. Changes the relation into a proxy so we can add/remove links. This sets the caller on the source side of the relation.
51 52 53 54 |
# File 'app/models/relation_proxy.rb', line 51 def source=(start) @start = start @side = :source end |
#target=(start) ⇒ Object
Define the caller’s side. Changes the relation into a proxy so we can add/remove links. This sets the caller on the target side of the relation.
57 58 59 60 |
# File 'app/models/relation_proxy.rb', line 57 def target=(start) @start = start @side = :target end |
#this_kpath ⇒ Object
234 235 236 |
# File 'app/models/relation_proxy.rb', line 234 def this_kpath @side == :source ? source_kpath : target_kpath end |
#this_role ⇒ Object
114 115 116 |
# File 'app/models/relation_proxy.rb', line 114 def this_role @side == :source ? self[:source_role] : self[:target_role] end |
#unique? ⇒ Boolean
441 442 443 |
# File 'app/models/relation_proxy.rb', line 441 def unique? @side == :source ? target_unique : source_unique end |
#update_links! ⇒ Object
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'app/models/relation_proxy.rb', line 421 def update_links! return unless @attributes_to_update @del_links.each { |l| l.destroy } @update_links.each { |l| l.save } return if @add_links == [] list = [] @add_links.each do |hash| next if hash[:id].blank? list << ([self[:id], @start[:id], hash[:id]] + LINK_ATTRIBUTES.map{|sym| hash[sym]}) end Zena::Db.insert_many('links', ['relation_id', link_side, other_side] + LINK_ATTRIBUTES, list) @attributes_to_update = nil @links_to_delete = nil remove_instance_variable(:@records) if defined?(@records) remove_instance_variable(:@record) if defined?(@record) remove_instance_variable(:@other_links) if defined?(@other_links) end |