root/trunk/lib/has_relations.rb

Revision 1259, 8.4 kB (checked in by gaspard, 2 months ago)

commit 5c1378c03854bd32ed765a941cdd187647bc51fd
Author: Gaspard Bucher <gaspard@teti.ch>

typos

Line 
1 module Zena
2   module Relations
3     module HasRelations
4       # this is called when the module is included into the 'base' module
5       def self.included(base)
6         base.extend Zena::Relations::TriggerClassMethod
7       end
8     end
9
10     module TriggerClassMethod
11       def has_relations
12         validate      :relations_valid
13         after_save    :update_relations
14         after_destroy :destroy_links
15        
16         class_eval <<-END
17           include Zena::Relations::InstanceMethods
18           class << self
19             include Zena::Relations::ClassMethods
20           end
21          
22           def relation_base_class
23             #{self}
24           end
25          
26           HAS_RELATIONS = true
27         END
28       end
29     end
30    
31     module ClassMethods
32       # All relations related to the current class/virtual_class with its ancestors.
33       def all_relations(start=nil)
34         rel_as_source = RelationProxy.find(:all, :conditions => ["site_id = ? AND source_kpath IN (?)", current_site[:id], split_kpath])
35         rel_as_target = RelationProxy.find(:all, :conditions => ["site_id = ? AND target_kpath IN (?)", current_site[:id], split_kpath])
36         rel_as_source.each {|rel| rel.source = start } if start
37         rel_as_target.each {|rel| rel.target = start } if start
38         (rel_as_source + rel_as_target).sort {|a,b| a.other_role <=> b.other_role}
39       end
40      
41       # Class path hierarchy. Example for (Post) : N, NN, NNP
42       def split_kpath
43         @split_kpath ||= begin
44           klasses   = []
45           kpath.split(//).each_index { |i| klasses << kpath[0..i] }
46           klasses
47         end
48       end
49     end
50    
51     module InstanceMethods
52      
53       # status defined through loading link
54       def l_status
55         return @l_status if defined? @l_status
56         val = @link ? @link[:status] : self['l_status']
57         val ? val.to_i : nil
58       end
59      
60       # comment defined through loading link
61       def l_comment
62         return @l_comment if defined? @l_comment
63         @link ? @link[:comment] : self['l_comment']
64       end
65      
66       def link_id
67         @link ? @link[:id] : (self[:link_id] == -1 ? nil : self[:link_id]) # -1 == dummy link
68       end
69      
70       def link_id=(v)
71         if @link && @link[:id].to_i != v.to_i
72           @link = nil
73         end
74         self[:link_id] = v.to_i
75         if @link_attributes_to_update
76           if rel = relation_proxy_from_link
77             @link_attributes_to_update.each do |k,v|
78               rel.send("other_#{k}=",v)
79             end
80           end
81         end
82       end
83      
84       def add_link(role, hash)
85         if rel = relation_proxy(role)
86           [:status, :comment, :id].each do |k|
87             rel.send("other_#{k}=", hash[k]) if hash.has_key?(k)
88           end
89         else
90           errors.add(role, 'invalid relation')
91         end
92       end
93      
94       def remove_link(link)
95         if link[:source_id] != self[:id] && link[:target_id] != self[:id]
96           errors.add('link', "not related to this node")
97           return false
98         end
99         # find proxy
100         if rel = relation_proxy_from_link(link)
101           rel.remove_link(link)
102         else
103           errors.add('link', "cannot remove (relation proxy not found).")
104         end
105       end
106      
107       def l_comment=(v)
108         @l_comment = v.blank? ? nil : v
109         if rel = relation_proxy_from_link
110           rel.other_comment = @l_comment
111         end
112       end
113      
114       def l_status=(v)
115         @l_status = v.blank? ? nil : v
116         if rel = relation_proxy_from_link
117           rel.other_status = @l_status
118         end
119       end
120      
121       def all_relations
122         @all_relations ||= self.vclass.all_relations(self)
123       end
124      
125       def relations_for_form
126         all_relations.map {|r| [r.other_role.singularize, r.other_role]}
127       end
128      
129       # List the links, grouped by role
130       def relation_links
131         res = []
132         all_relations.each do |rel|
133           #if relation.record_count > 5
134           #  # FIXME: show message ?
135           #end
136           links = rel.records(:limit => 5, :order => "link_id DESC")
137           res << [rel, links] if links
138         end
139         res
140       end
141      
142       # Find relation proxy for the given role.
143       def relation_proxy(role)
144         @relation_proxies ||= {}
145         return @relation_proxies[role] if @relation_proxies.has_key?(role)
146         @relation_proxies[role] = RelationProxy.get_proxy(self, role.singularize.underscore)
147       end
148      
149       def relation_proxy_from_link(link = nil)
150         unless link
151           if @link
152             link = @link
153           elsif self.link_id
154             link = @link = Link.find_through(self, self.link_id)
155           end
156           return nil unless link
157         end
158         @relation_proxies ||= {}
159         return @relation_proxies[link.role] if @relation_proxies.has_key?(link.role)
160         @relation_proxies[link.role] = link.relation_proxy(self)
161       end
162      
163       private
164      
165         # Used to create / destroy / update links through pseudo methods 'icon_id=', 'icon_status=', ...
166         # Pseudo methods created for a many-to-one relation (icon_for --- icon):
167         # icon_id=::      set icon
168         # icon_status=::  set status field for link to icon
169         # icon_comment=:: set comment field for link to icon
170         # icon_for_ids=:: set all nodes for which the image is an icon (replaces old values)
171         # icon_for_id=::  add a node for which the image is an icon (adds a new value)
172         # icon_id::       get icon id
173         # icon_zip::      get icon zip
174         # icon_status::   get status field for link to icon
175         # icon_comment::  get comment field for link to icon
176         # icon_for_ids::  get all node ids for which the image is an icon
177         # icon_for_zips:: get all node zips for which the image is an icon
178         def method_missing(meth, *args)
179           # first try rails' version of method missing
180           super
181         rescue NoMethodError => err
182           # 1. is this a method related to a relation ?
183           if meth.to_s =~ /^([\w_]+)_(ids?|zips?|status|comment)(=?)$/
184             role  = $1
185             field = $2
186             mode  = $3
187             # 2. is this a valid role ?
188             if rel = relation_proxy(role)
189               if mode == '='
190                 # set
191                 case field
192                 when 'zip', 'zips'
193                   # not used to set relations (must use 'translate_attributes' to chagen zip into id before call)
194                   raise err
195                 end
196                 # set value
197                 rel.send("other_#{field}=", args[0])
198               else
199                 # get
200                 if field != 'ids' && field != 'zips' && !rel.unique?
201                   # ask for a single value in a ..-to-many relation
202                   # 1. try to use focus
203                   if @link
204                     rel.other_link = @link
205                   elsif self.link_id
206                     @link = Link.find_through(self, self.link_id)
207                     rel.other_link = @link
208                   else
209                     return nil
210                   end
211                 end
212                 rel.send("other_#{field}")
213               end
214             else
215               # invalid relation
216               if mode == '='
217                 errors.add(role, "invalid relation") unless args[0].blank?
218                 return args[0]
219               else
220                 # ignore
221                 return nil
222               end
223             end
224           else
225             # not related to relations
226             raise err
227           end
228         end
229      
230         # Make sure all updated relation proxies are valid
231         def relations_valid
232           return true unless @relation_proxies
233           @relation_proxies.each do |role, rel|
234             next unless rel
235             unless rel.attributes_to_update_valid?
236               errors.add(role, rel.link_errors.join(', '))
237             end
238           end
239         end
240        
241         # Update/create links defined in relation proxies
242         def update_relations
243           return unless @relation_proxies
244           @relation_proxies.each do |role, rel|
245             next unless rel
246             rel.update_links!
247           end
248         end
249        
250         # Destroy all links related to this node
251         def destroy_links
252           Link.find(:all, :conditions => ["source_id = ? OR target_id = ?", self[:id], self[:id]]).each do |l|
253             l.destroy
254           end
255         end
256     end
257   end
258 end
259
260 ActiveRecord::Base.send :include, Zena::Relations::HasRelations
Note: See TracBrowser for help on using the browser.