patriarch 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -17,7 +17,9 @@ and keep track of all this in one transaction.
17
17
  Patriarch allows you to handle all of this in a matter of seconds and
18
18
  - gathers all of the redis calls in one transaction object that stores a redis queue ready to be processed
19
19
  - rollbacks SQL destroy transaction if redis database dropped while processing the instruction queue to clean data relative
20
- to SQL rows destroyed. Reduces possibilities of painful database incoherences
20
+ to SQL rows destroyed. Reduces possibilities of painful database incoherence.
21
+
22
+ Note : We use Patriarch in production
21
23
 
22
24
  # What Patriarch is not
23
25
 
@@ -26,8 +28,14 @@ Patriarch is not a replacement for SQL
26
28
  ## Installation
27
29
 
28
30
  Add this line to your application's Gemfile:
31
+ ```ruby
32
+ gem 'patriarch'
33
+ ```
29
34
 
30
- gem 'patriarch'
35
+ Or this one if you want to be up-to-date in real time
36
+ ```ruby
37
+ gem 'patriarch', :git => 'https://github.com/nateware/redis-objects.git'
38
+ ```
31
39
 
32
40
  And then execute:
33
41
 
@@ -40,11 +48,11 @@ Or install it yourself as:
40
48
  ## Usage
41
49
 
42
50
  First include it into your model:
43
-
44
- class User < ActiveRecord::Base
45
- include Patriarch::Behaviours
46
- end
47
-
51
+ ```ruby
52
+ class User < ActiveRecord::Base
53
+ include Patriarch::Behaviours
54
+ end
55
+ ```
48
56
  ### Initializing files for a behaviour
49
57
 
50
58
  Just type in:
@@ -55,10 +63,168 @@ Just type in:
55
63
 
56
64
  Add a behaviour in a simple way and write this just below the include:
57
65
 
58
- add_behaviour :on => [Model1,Model2] # add active_behaviour performed on instances of model1, model2
59
- add_behaviour :by => [Model3] # add passive_behaviour performed on us by instances of model3
66
+ ```ruby
67
+ add_behaviour :behaviour, :on => [:model1,:model2] # add active_behaviour performed on instances of model1, model2
68
+ add_behaviour :behaviour, :by => :model3 # add passive_behaviour performed on us by instances of model3
69
+ ```
60
70
 
61
71
  It works like belongs_to and has_many helpers of active record, you need to include declarations in both models
72
+ Let's say we have a Users that we want it to be able to like instances from model Post and Message. You may want to use
73
+ aliases like did below with as and undo_as options
74
+ ```ruby
75
+ class User < ActiveRecord::Base
76
+ include Patriarch::Behaviours
77
+ add_behaviour :like, :on => [:notification,:post], :as => :love, :undo_as => :hate
78
+ end
79
+
80
+ class Post < ActiveRecord::Base
81
+ include Patriarch::Behaviours
82
+ add_behaviour :like, :by => :user
83
+ end
84
+
85
+ class Message < ActiveRecord::Base
86
+ include Patriarch::Behaviours
87
+ add_behaviour :like, :by => :user
88
+ end
89
+ ```
90
+ This will provide 'behaviour' methods and tool methods whose name are built like this :
91
+ ```ruby
92
+ "#{actor_model_name.pluralize}_#{behaviour_in_progressive_present_form}_me" # tool method to retrieve models that acted on me
93
+ "#{target_model_name.pluralize}_i_#{behaviour}" # tool method to retrieve models i acted on
94
+ ```
95
+ Adding suffix ids will prevent costly model instantiations and requesting the database and only return ids as an array
96
+ of Fixnum
97
+
98
+ And now with the example ...
99
+ ```ruby
100
+ # Let's grab an user a message and a post, we assume some are already created
101
+ user = User.first
102
+ post = Post.first
103
+ mess = Message.find(10)
104
+
105
+ user.like post
106
+ user.love mess
107
+
108
+ user.posts_i_like # => [post]
109
+ user.messages_i_like # => [mess]
110
+ user.posts_i_love # => [post]
111
+
112
+ user.posts_i_like_ids # => [1]
113
+ useu.messages_i_like_ids # => [10]
114
+ useu.messages_i_love_ids # => [10]
115
+
116
+ post.users_loving_me # => [user]
117
+ mess.users_liking_me_ids # => [1]
118
+
119
+ user.undo_like mess
120
+ user.messages_i_like # => []
121
+ user.messages_i_like_ids # => []
122
+
123
+ user.hate post
124
+ user.posts_i_like # => []
125
+ user.posts_i_love_ids # => []
126
+ ```
127
+ ### Tripartite relations
128
+
129
+ You can also define tripartite behaviours. An example we use is that you can comment a post via a message.
130
+ Tripartite behaviours implies that a medium is used to perform an action.
131
+ For our little example that would become :
132
+
133
+ ```ruby
134
+ class User < ActiveRecord::Base
135
+ include Patriarch::Behaviours
136
+ add_behaviour :comment, :on => :post, :via => :message
137
+ end
138
+
139
+ class Post < ActiveRecord::Base
140
+ include Patriarch::Behaviours
141
+ add_behaviour :comment, :by => :user, :via => :message
142
+ end
143
+
144
+ class Message < ActiveRecord::Base
145
+ include Patriarch::Behaviours
146
+ add_behaviour :comment, :medium_between => [:user,:post]
147
+ end
148
+ ```
149
+
150
+ This will provide 'behaviour' methods and tool methods whose name are built like this :
151
+ ```ruby
152
+ "#{actor_model_name.pluralize}_#{behaviour_in_progressive_present_form}_me_via_#{medium_model_name.pluralize}"
153
+ # tool method to retrieve models that acted on me through medium
154
+
155
+ "#{target_model_name.pluralize}_i_#{behaviour}_via_#{medium_model_name.pluralize}"
156
+ # tool method to retrieve models i acted on me through medium
157
+
158
+ "#{actor_model_name.pluralize}_#{behaviour_in_progressive_present_form}_#{target_model_name.pluralize}_via_me"
159
+ # tool method to retrieve models that acted through me on target
160
+ ```
161
+
162
+ Adding suffix ids will prevent costly model instantiations and requesting the database and only return ids as an array
163
+ of Fixnum. See Options section to learn more about some detailed job.
164
+
165
+ And now with the example ...
166
+ ```ruby
167
+ # Let's grab an user a message and a post, we assume some are already created
168
+ user = User.first
169
+ post = Post.first
170
+ mess = Message.first
171
+
172
+ user.comment mess,post
173
+
174
+ user.posts_i_comment_via_messages # => [post]
175
+ user.posts_i_comment_via_messages_ids # => [1]
176
+
177
+ post.users_commenting_me_via_messages # => [user]
178
+ post.users_commenting_me_via_messages_ids # => [1]
179
+
180
+ mess.users_commenting_posts_via_me
181
+ # => [{:actor_type => User, :actor_id => 1, :medium_type => Message, :medium_id => 1, :target_type => Post, :target_id => 1}]
182
+
183
+ user.undo_like mess
184
+ user.messages_i_like # => []
185
+ user.messages_i_like_ids # => []
186
+
187
+ user.hate post
188
+ user.posts_i_like # => []
189
+ user.posts_i_love_ids # => []
190
+ ```
191
+
192
+ Also, aliases work for tripartite the same way they do for bipartite
193
+
194
+ ### Options
195
+ #### with_scores
196
+ Patriarch automatically stores interactions with a score that equals to Time.now.to_f. This is for the moment not possible
197
+ to change this and is a behaviour of Patriarch that one should be able to change in a near future.
198
+ You can hence passe the options :with_scores => true when calling tool methods
199
+
200
+ ```ruby
201
+ # Let's grab an user a message and a post, we assume some are already created
202
+ user = User.first
203
+ post = Post.first
204
+
205
+ user.like post
206
+
207
+ user.posts_i_like # => [post]
208
+ user.posts_i_like :with_scores => true # => [[post,123456.789123]]
209
+ ```
210
+ #### with_medium
211
+ Patriarch allows you to bring back the entire "who's who" information of a tripartite transaction
212
+ It will bring something like that if you take back the example we gave a while ago
213
+
214
+ ```ruby
215
+ user = User.first
216
+ post = Post.first
217
+ mess = Message.first
218
+
219
+ user.comment mess,post
220
+
221
+ user.posts_i_comment_via_messages # => [post]
222
+ user.posts_i_comment_via_messages_ids # => [1]
223
+ user.posts_i_comment_via_messages_ids :with_medium => true
224
+ # => [{:actor_type => User, :actor_id => 1, :medium_type => Message, :medium_id => 1, :target_type => Post, :target_id => 1}]
225
+ ```
226
+
227
+ "with_medium" option is not compatible with "with_scores" option
62
228
 
63
229
  ## Contributing
64
230
 
@@ -67,3 +233,8 @@ It works like belongs_to and has_many helpers of active record, you need to incl
67
233
  3. Commit your changes (`git commit -am 'Added some feature'`)
68
234
  4. Push to the branch (`git push origin my-new-feature`)
69
235
  5. Create new Pull Request
236
+
237
+ ## Thanks
238
+
239
+ Thanks for using this gem ! If you are really really grateful or want to talk about this and happen to be in Paris, you
240
+ can issue a beer pull request that i will happily merge.
@@ -33,4 +33,13 @@ class Patriarch::Services::<%= class_name %>::ManagerService < Patriarch::Manage
33
33
  # If everything went smoothly
34
34
  transac.execute if (transac_first_step && callbacks_were_completed)
35
35
  end
36
- end
36
+
37
+ def resolve_with_reduced_sql_database_calls(actor_class,actor_id,target_class,target_id,medium_class,medium_id)
38
+ actor = OpenStruct.new ; actor.id = actor_id
39
+ actor.define_singleton_method(:class) do
40
+ actor_class
41
+ end
42
+ target = OpenStruct.new ; target.id = target_id ; target.class = target_class
43
+ medium = OpenStruct.new ; medium.id = medium_id ; medium.class = medium_class
44
+ end
45
+ end
@@ -1,21 +1,30 @@
1
1
  require 'singleton'
2
2
 
3
+ # Mother class of all the Patriarch::__behaviour__::AuthorizationService
4
+ # We use singleton pattern here
3
5
  module Patriarch
6
+ class ForbiddenBehaviourException < Exception; end
7
+
4
8
  class AuthorizationService
5
9
  include Singleton
6
10
 
7
- # override if necessary
11
+ # All authorization services are called by method #grant
12
+ # Since type verification is an eternal we implement grant in the mother class and let daughter classes
13
+ # call it with super and benefit from verify_types or bypass it completely and override the function
8
14
  def grant?(transac)
9
- # Insert authorization logic here ...
10
- if verify_types(transac)
15
+ if check_types(transac)
11
16
  true
12
17
  else
13
- raise "that behaviour is not authorized"
18
+ raise Patriarch::ForbiddenBehaviourException, "that behaviour is not authorized"
14
19
  end
15
- #true
16
20
  end
17
21
 
18
- def verify_types(transac)
22
+ # When declaring behaviours in model thanks to add_behaviour helper we enforce that ONLY the behaviour declared
23
+ # are authorized. We hence verify that when a behaviour is called.
24
+ # For example User could be able to like Items and thus be blessed with #like as an instance method.
25
+ # But then we can tell any user instance to like any object, this method denies these behaviours.
26
+ # @transac [Patriarch::Transaction] the transaction to be authorized (or not)
27
+ def check_types(transac)
19
28
  actor_model = transac.actor.class
20
29
  actor_model_sym = actor_model.name.underscore.to_sym
21
30
 
@@ -29,19 +38,21 @@ module Patriarch
29
38
  auths = []
30
39
 
31
40
  if transac.tripartite?
32
- # Verif for actor
41
+ # Verification for actor
33
42
  auths << actor_model.patriarch_behaviours[behaviour][:on].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(target_model_sym)
34
43
  auths << actor_model.patriarch_behaviours[behaviour][:via].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(medium_model_sym)
35
44
 
36
- # Verif for target
45
+ # Verification for target
37
46
  auths << target_model.patriarch_behaviours[behaviour][:by].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(actor_model_sym)
38
47
  auths << target_model.patriarch_behaviours[behaviour][:via].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(medium_model_sym)
39
48
 
40
- # Verif for medium
41
- auths << medium_model.patriarch_behaviours[behaviour][:medium_between].map{ |dual_sym_tab| dual_sym_tab.map{ |b_sym| b_sym.to_s.underscore.to_sym } }.include?(
42
- [actor_model_sym, target_model_sym]
43
- )
44
- # cas particulier
49
+ # Verification for medium
50
+ # Pay attention here since add_behaviour syntax is different when being a medium
51
+ auths << medium_model.patriarch_behaviours[behaviour][:medium_between].map do |dual_sym_tab|
52
+ dual_sym_tab.map{ |b_sym| b_sym.to_s.underscore.to_sym }
53
+ end.include?(
54
+ [actor_model_sym, target_model_sym]
55
+ )
45
56
  else
46
57
  auths << actor_model.patriarch_behaviours[behaviour][:on].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(target_model_sym)
47
58
  auths << target_model.patriarch_behaviours[behaviour][:by].map{ |b_sym| b_sym.to_s.underscore.to_sym }.include?(actor_model_sym)
@@ -20,13 +20,17 @@ module Patriarch
20
20
  class << self
21
21
 
22
22
  # Helper function to complete the anonymous module we include later in the process in the enhanced model class
23
- def complete_custom_active_module_bipartite(module_to_complete,relation_type,acted_on_model_list,options={})
24
- module_to_complete.class_eval do
23
+ # @param [Module] anon_module refers to the anonymous module to be completed
24
+ # @param [String] behaviour the behaviour for which we complete the module with tools method
25
+ # @param [Array] acted_on_model_list the list of model classes that i can target with the behaviour
26
+ # @param [Hash] options options can serve to build custom alias for tool methods
27
+ def complete_custom_active_module_bipartite(anon_module,behaviour,acted_on_model_list,options={})
28
+ anon_module.class_eval do
25
29
 
26
30
  acted_on_model_list.each do |acted_on_model|
27
31
 
28
32
  # Compute names based on conventions
29
- classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{relation_type.to_s}"
33
+ classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{behaviour}"
30
34
  raw_tool_method_name = classic_tool_method_name + "_ids"
31
35
  redis_key = "patriarch_" + classic_tool_method_name
32
36
 
@@ -37,29 +41,35 @@ module Patriarch
37
41
  get_models_from_ids(self,acted_on_model.to_s.classify.constantize,raw_tool_method_name,options)
38
42
  end
39
43
  if options[:as]
40
- alias_for_classic = classic_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
41
- alias_method alias_for_classic, classic_tool_method_name
42
- end
44
+ alias_for_classic = classic_tool_method_name.sub(behaviour, options[:as].to_s)
45
+ alias_method alias_for_classic, classic_tool_method_name
46
+ end
43
47
 
44
48
  define_method(raw_tool_method_name) do |options={}|
45
- Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
49
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
46
50
  end
47
51
  if options[:as]
48
- alias_for_raw = raw_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
52
+ alias_for_raw = raw_tool_method_name.sub(behaviour, options[:as].to_s)
49
53
  alias_method alias_for_raw, raw_tool_method_name
50
- end
54
+ end
51
55
  end
52
56
  end
53
57
  end
54
58
 
55
- def complete_custom_passive_module_bipartite(module_to_complete,relation_type,targetted_by_model_list,options={})
56
- module_to_complete.class_eval do
57
59
 
58
- targetted_by_model_list.each do |targetted_by_model|
60
+ # Helper function to complete the anonymous module we include later in the process in the enhanced model class
61
+ # @param [Module] anon_module refers to the anonymous module to be completed
62
+ # @param [String] behaviour the behaviour for which we complete the module with tools method
63
+ # @param [Array] targeted_by_model_list the list of model classes that can target me with the behaviour
64
+ # @param [Hash] options options can serve to build custom alias for tool methods
65
+ def complete_custom_passive_module_bipartite(anon_module,behaviour,targeted_by_model_list,options={})
66
+ anon_module.class_eval do
67
+
68
+ targeted_by_model_list.each do |targeted_by_model|
59
69
 
60
- # Compute names based on conventions
61
- progressive_present_relation_type = (Verbs::Conjugator.conjugate relation_type.to_sym, :aspect => :progressive).split(/ /).last
62
- classic_tool_method_name = "#{targetted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me"
70
+ # Compute names based on conventions
71
+ progressive_present_relation_type = (Verbs::Conjugator.conjugate behaviour.to_sym, :aspect => :progressive).split(/ /).last
72
+ classic_tool_method_name = "#{targeted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me"
63
73
  raw_tool_method_name = classic_tool_method_name + "_ids"
64
74
  redis_key = "patriarch_" + classic_tool_method_name
65
75
 
@@ -67,43 +77,49 @@ module Patriarch
67
77
  # Redis key has the same radical as the method in class by convention (but has a patriarch_ prefix to avoid collisions with other gems)
68
78
  define_method(classic_tool_method_name) do |options={}|
69
79
  Patriarch::ToolServices::RedisExtractorService.instance.
70
- get_models_from_ids(self,targetted_by_model.to_s.classify.constantize,raw_tool_method_name,options)
80
+ get_models_from_ids(self,targeted_by_model.to_s.classify.constantize,raw_tool_method_name,options)
71
81
  end
72
82
  if options[:as]
73
- progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
83
+ progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
74
84
  alias_for_classic = classic_tool_method_name.sub(progressive_present_relation_type,progressive_present_relation_type_alias)
75
85
  alias_method alias_for_classic, classic_tool_method_name
76
86
  end
77
87
 
78
88
  define_method(raw_tool_method_name) do |options={}|
79
- Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
89
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
80
90
  end
81
91
  if options[:as]
82
- progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
92
+ progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
83
93
  alias_for_raw = raw_tool_method_name.sub(progressive_present_relation_type,progressive_present_relation_type_alias)
84
94
  alias_method alias_for_raw, raw_tool_method_name
85
- end
95
+ end
86
96
 
87
97
  end
88
98
 
89
99
  end
90
- end
100
+ end
91
101
 
92
- def complete_custom_active_module_tripartite(module_to_complete,relation_type,acted_on_model_list,via_model_list,options={})
93
- module_to_complete.class_eval do
102
+ # Helper function to complete the anonymous module we include later in the process in the enhanced model class
103
+ # @param [Module] anon_module refers to the anonymous module to be completed
104
+ # @param [String] behaviour the behaviour for which we complete the module with tools method
105
+ # @param [Array] acted_on_model_list the list of model classes that i can target with the behaviour
106
+ # @param [Array] via_model_list the list of model classes that the behaviour use to act through
107
+ # @param [Hash] options options can serve to build custom alias for tool methods
108
+ def complete_custom_active_module_tripartite(anon_module,behaviour,acted_on_model_list,via_model_list,options={})
109
+ anon_module.class_eval do
94
110
 
95
111
  acted_on_model_list.each do |acted_on_model|
96
112
  via_model_list.each do |via_model|
97
113
 
98
114
  # Compute names based on conventions
99
115
  # fallen_angels_i_praise_via_love_letters
100
- target_classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{relation_type.to_s}_via_#{via_model.to_s.tableize}"
116
+ target_classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{behaviour}_via_#{via_model.to_s.tableize}"
101
117
  target_raw_tool_method_name = target_classic_tool_method_name + "_ids"
102
118
 
103
119
  # love_letters_i_use_to_praise_fallen_angels
104
- medium_classic_tool_method_name = "#{via_model.to_s.tableize}_i_use_to_#{relation_type.to_s}_#{acted_on_model.to_s.tableize}"
105
- medium_raw_tool_method_name = medium_classic_tool_method_name + "_ids"
106
-
120
+ medium_classic_tool_method_name = "#{via_model.to_s.tableize}_i_use_to_#{behaviour}_#{acted_on_model.to_s.tableize}"
121
+ medium_raw_tool_method_name = medium_classic_tool_method_name + "_ids"
122
+
107
123
  redis_key = "patriarch_" + target_classic_tool_method_name
108
124
 
109
125
  # Define methods with the pattern : items_i_like that returns models and items_i_like_ids that return
@@ -113,17 +129,17 @@ module Patriarch
113
129
  get_models_from_ids(self,acted_on_model.to_s.classify.constantize,target_raw_tool_method_name,options.merge({:tripartite => true}))
114
130
  end
115
131
  if options[:as]
116
- alias_for_classic = target_classic_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
117
- alias_method alias_for_classic, target_classic_tool_method_name
118
- end
132
+ alias_for_classic = target_classic_tool_method_name.sub(behaviour, options[:as].to_s)
133
+ alias_method alias_for_classic, target_classic_tool_method_name
134
+ end
119
135
 
120
136
  define_method(target_raw_tool_method_name) do |options={}|
121
137
  Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true, :protagonist_type => :target}))
122
138
  end
123
139
  if options[:as]
124
- alias_for_target_raw = target_raw_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
140
+ alias_for_target_raw = target_raw_tool_method_name.sub(behaviour, options[:as].to_s)
125
141
  alias_method alias_for_target_raw, target_raw_tool_method_name
126
- end
142
+ end
127
143
 
128
144
  #
129
145
  define_method(medium_classic_tool_method_name) do |options={}|
@@ -131,48 +147,54 @@ module Patriarch
131
147
  get_models_from_ids(self,via_model.to_s.classify.constantize,medium_raw_tool_method_name,options.merge({:tripartite => true}))
132
148
  end
133
149
  if options[:as]
134
- alias_for_medium_classic = medium_classic_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
135
- alias_method alias_for_medium_classic, medium_classic_tool_method_name
136
- end
150
+ alias_for_medium_classic = medium_classic_tool_method_name.sub(behaviour, options[:as].to_s)
151
+ alias_method alias_for_medium_classic, medium_classic_tool_method_name
152
+ end
137
153
  #
138
154
 
139
155
  define_method(medium_raw_tool_method_name) do |options={}|
140
156
  Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true, :protagonist_type => :medium}))
141
157
  end
142
158
  if options[:as]
143
- alias_for_medium_raw = medium_raw_tool_method_name.sub(relation_type.to_s, options[:as].to_s)
159
+ alias_for_medium_raw = medium_raw_tool_method_name.sub(behaviour, options[:as].to_s)
144
160
  alias_method alias_for_medium_raw, medium_raw_tool_method_name
145
- end
161
+ end
146
162
 
147
163
  end
148
164
  end
149
165
 
150
- end
166
+ end
151
167
  end
152
168
 
153
- def complete_custom_passive_module_tripartite(module_to_complete,relation_type,targetted_by_model_list,via_model_list,options={})
154
- module_to_complete.class_eval do
155
-
156
- targetted_by_model_list.each do |targetted_by_model|
169
+ # Helper function to complete the anonymous module we include later in the process in the enhanced model class
170
+ # @param [Module] anon_module refers to the anonymous module to be completed
171
+ # @param [String] behaviour the behaviour for which we complete the module with tools method
172
+ # @param [Array] targeted_by_model_list the list of model classes that can target me with the behaviour
173
+ # @param [Array] via_model_list the list of model classes that the behaviour use to act through
174
+ # @param [Hash] options options can serve to build custom alias for tool methods
175
+ def complete_custom_passive_module_tripartite(anon_module,behaviour,targeted_by_model_list,via_model_list,options={})
176
+ anon_module.class_eval do
177
+
178
+ targeted_by_model_list.each do |targeted_by_model|
157
179
  via_model_list.each do |via_model|
158
- # Compute names based on conventions
159
- progressive_present_relation_type = (Verbs::Conjugator.conjugate relation_type.to_sym, :aspect => :progressive).split(/ /).last
160
-
161
- actor_classic_tool_method_name = "#{targetted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me_via_#{via_model.to_s.tableize}"
180
+ # Compute names based on conventions
181
+ progressive_present_relation_type = (Verbs::Conjugator.conjugate behaviour.to_sym, :aspect => :progressive).split(/ /).last
182
+
183
+ actor_classic_tool_method_name = "#{targeted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me_via_#{via_model.to_s.tableize}"
162
184
  actor_raw_tool_method_name = actor_classic_tool_method_name + "_ids"
163
-
185
+
164
186
  #"love_letters_used_by_monsters_to_praise_me"
165
- medium_classic_tool_method_name = "#{via_model.to_s.tableize}_used_by_#{targetted_by_model.to_s.tableize}_to_#{relation_type.to_s}_me"
187
+ medium_classic_tool_method_name = "#{via_model.to_s.tableize}_used_by_#{targeted_by_model.to_s.tableize}_to_#{behaviour}_me"
166
188
  medium_raw_tool_method_name = medium_classic_tool_method_name + "_ids"
167
-
189
+
168
190
  redis_key = "patriarch_" + actor_classic_tool_method_name
169
191
 
170
192
  define_method(actor_classic_tool_method_name) do |options={}|
171
193
  Patriarch::ToolServices::RedisExtractorService.instance.
172
- get_models_from_ids(self,targetted_by_model.to_s.classify.constantize,actor_raw_tool_method_name,options.merge({:tripartite => true}))
194
+ get_models_from_ids(self,targeted_by_model.to_s.classify.constantize,actor_raw_tool_method_name,options.merge({:tripartite => true}))
173
195
  end
174
196
  if options[:as]
175
- progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
197
+ progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
176
198
  alias_for_actor_classic = actor_classic_tool_method_name.sub(progressive_present_relation_type,progressive_present_relation_type_alias)
177
199
  alias_method alias_for_actor_classic, actor_classic_tool_method_name
178
200
  end
@@ -181,36 +203,41 @@ module Patriarch
181
203
  Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true , :protagonist_type => :actor}))
182
204
  end
183
205
  if options[:as]
184
- progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
206
+ progressive_present_relation_type_alias = (Verbs::Conjugator.conjugate options[:as], :aspect => :progressive).split(/ /).last
185
207
  alias_for_actor_raw = actor_raw_tool_method_name.sub(progressive_present_relation_type,progressive_present_relation_type_alias)
186
208
  alias_method alias_for_actor_raw, actor_raw_tool_method_name
187
- end
209
+ end
188
210
 
189
211
  define_method(medium_classic_tool_method_name) do |options={}|
190
212
  Patriarch::ToolServices::RedisExtractorService.instance.
191
213
  get_models_from_ids(self,via_model.to_s.classify.constantize,medium_raw_tool_method_name,options.merge({:tripartite => true}))
192
- end
214
+ end
193
215
  #TODO add alias there
194
216
  # FIXME id in it is wrong ...
195
217
  define_method(medium_raw_tool_method_name) do |options={}|
196
218
  Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true , :protagonist_type => :medium}))
197
- end
219
+ end
198
220
 
199
221
  end
200
222
  end
201
223
 
202
224
  end
203
- end
225
+ end
204
226
 
227
+ #:nodoc:
205
228
  def included(klass)
229
+ # Classic extend of ClassMethods module embedded within this module
206
230
  klass.extend ClassMethods
207
- #klass.extend ActiveModel::Callbacks
208
231
  unless klass.respond_to?(:before_destroy) && klass.respond_to?(:after_destroy)
209
232
  raise AddBehaviourDependencyError, "class #{klass.name} including Patriarch::Behaviours does not support callbacks"
210
233
  end
211
234
 
235
+ # insert callback logic here : behaviours have to be cleaned when a destroy occur in SQL
236
+ # First we build the transaction with #compute_redis_dependencies
237
+ # We execute the clean only when the SQL transaction commit to avoid data inconsistency if a SQL Rollback occurs
238
+ # in a middle of a cascading transaction
212
239
  klass.class_eval do
213
-
240
+
214
241
  before_destroy :compute_redis_dependencies
215
242
  def compute_redis_dependencies
216
243
  @redis_destroy_transac_object = Patriarch::ToolServices::RedisCleanerService.instance.clean_all(self)
@@ -240,40 +267,47 @@ module Patriarch
240
267
  end
241
268
  end
242
269
 
270
+ # Gathers class methods that should be available for model that include Patriarch::Behaviours
243
271
  module ClassMethods
244
272
 
245
- def check_add_behaviour_syntax(behaviour,options)
273
+
274
+ # Helper that checks if options syntax is correct
275
+ # Raises errors if not with indications on what should be corrected
276
+ # @param [Object] options
277
+ def check_add_behaviour_syntax(options)
246
278
  if options[:medium_between] || options[:via]
247
- check_tripartite_add_behaviour_syntax(behaviour,options)
279
+ check_tripartite_add_behaviour_syntax(options)
248
280
  elsif options[:on] || options[:by]
249
- check_bipartite_add_behaviour_syntax(behaviour,options)
281
+ check_bipartite_add_behaviour_syntax(options)
250
282
  end
251
283
  end
252
284
 
253
- def check_bipartite_add_behaviour_syntax(behaviour,options)
285
+ #:nodoc:
286
+ def check_bipartite_add_behaviour_syntax(options)
254
287
  # Either you add a behaviour on another model or allow a behaviour on you by another model
255
288
  unless options[:by].nil? ^ options[:on].nil?
256
289
  raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
257
290
  end
258
-
259
- # as option should be a string or symbol
291
+
292
+ # as option should be a string or symbol
260
293
  if options[:as]
261
- unless [Symbol,String].include?(options[:as].class)
294
+ unless [Symbol,String].include?(options[:as].class)
262
295
  raise AddBehaviourSyntaxError, "as option should be a string or symbol"
263
- end
296
+ end
264
297
  end
265
- end
298
+ end
266
299
 
267
- def check_tripartite_add_behaviour_syntax(behaviour,options)
300
+ #:nodoc:
301
+ def check_tripartite_add_behaviour_syntax(options)
268
302
  # Xor on options :medium_between and :via, disparate cases ...
269
303
  unless options[:medium_between].nil? ^ options[:via].nil?
270
304
  raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
271
- end
305
+ end
272
306
 
273
307
  if options[:via]
274
308
  unless options[:by].nil? ^ options[:on].nil?
275
309
  raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
276
- end
310
+ end
277
311
  else
278
312
  medium_between_option = options[:medium_between]
279
313
  unless medium_between_option.is_a?(Array) && medium_between_option.size == 2
@@ -282,8 +316,8 @@ module Patriarch
282
316
  end
283
317
 
284
318
  if options[:as]
285
- # as option should be a string or symbol
286
- unless [Symbol,String].include?(options[:as].class)
319
+ # as option should be a string or symbol
320
+ unless [Symbol,String].include?(options[:as].class)
287
321
  raise AddBehaviourSyntaxError, "as option should be a string or symbol"
288
322
  end
289
323
  end
@@ -299,7 +333,11 @@ module Patriarch
299
333
  end
300
334
  end
301
335
 
302
- def add_aliases_for_functionnalities(module_to_complete,behaviour,options)
336
+ # @param [Module] module_to_complete
337
+ # @param [String] behaviour
338
+ # @param [Object] options
339
+ # Adds alias for behaviour calls available to models if options allow to do so
340
+ def add_aliases_for_functionality(module_to_complete,behaviour,options)
303
341
  if options[:as]
304
342
  behaviour_alias = options[:as].to_s
305
343
  end
@@ -308,25 +346,30 @@ module Patriarch
308
346
  behaviour_undo_alias = options[:undo_as].to_s
309
347
  end
310
348
 
311
- if options[:on]
349
+ if options[:on]
312
350
  module_to_complete.instance_eval do
313
351
  # add behaviour_alias
314
- if options[:as]
352
+ if options[:as]
315
353
  alias_method behaviour_alias, behaviour
316
354
  end
317
355
 
318
356
  # add undo_behaviour_alias
319
- if options[:undo_as]
357
+ if options[:undo_as]
320
358
  alias_method behaviour_undo_alias, Patriarch.undo(behaviour)
321
- end
359
+ end
322
360
  end
323
- end
361
+ end
324
362
  end
325
363
 
326
- def add_functionnalities_for_bipartite(module_to_complete,behaviour,options)
327
- module_to_complete.instance_eval do
364
+ # @param [Module] anon_module
365
+ # @param [String] behaviour
366
+ # Add basic behaviours calls to models in bipartite situations
367
+ def add_functionality_for_bipartite(anon_module,behaviour)
368
+ anon_module.instance_eval do
328
369
 
329
370
  instance_eval do
371
+ # in instance_eval situations, there is no scope change so if we want to define included as a "self" method
372
+ # we are obliged to use this
330
373
  (class << self; self; end).send(:define_method,:included) do |klass|
331
374
  klass.extend ActiveModel::Callbacks
332
375
  klass.send(:define_model_callbacks, behaviour, Patriarch.undo(behaviour))
@@ -336,24 +379,29 @@ module Patriarch
336
379
  # behave
337
380
  define_method(behaviour) do |entity,options={}|
338
381
  run_callbacks behaviour do
339
- "Patriarch::Services::#{behaviour.classify}::ManagerService".constantize.instance.resolve(self,entity,options)
382
+ "Patriarch::Services::#{behaviour.classify}::ManagerService".constantize.instance.resolve(self,entity,options)
340
383
  end
341
384
  end
342
385
 
343
386
  # undo_behave
344
387
  define_method(Patriarch.undo(behaviour).to_sym) do |entity,options={}|
345
388
  run_callbacks Patriarch.undo(behaviour).to_sym do
346
- "Patriarch::Services::#{(Patriarch.undo(behaviour)).classify}::ManagerService".constantize.instance.resolve(self,entity,options)
389
+ "Patriarch::Services::#{(Patriarch.undo(behaviour)).classify}::ManagerService".constantize.instance.resolve(self,entity,options)
347
390
  end
348
391
  end
349
392
 
350
393
  end # instance_eval
351
394
  end
352
395
 
353
- def add_functionnalities_for_tripartite(module_to_complete,behaviour,options)
354
- module_to_complete.instance_eval do
396
+ # @param [Module] anon_module
397
+ # @param [String] behaviour
398
+ # Add basic behaviours calls to models in tripartite situations
399
+ def add_functionality_for_tripartite(anon_module,behaviour)
400
+ anon_module.instance_eval do
355
401
 
356
402
  instance_eval do
403
+ # in instance_eval situations, there is no scope change so if we want to define included as a "self" method
404
+ # we are obliged to use this
357
405
  (class << self; self; end).send(:define_method,:included) do |klass|
358
406
  klass.extend ActiveModel::Callbacks
359
407
  klass.send(:define_model_callbacks, behaviour, Patriarch.undo(behaviour))
@@ -363,7 +411,7 @@ module Patriarch
363
411
  # behave
364
412
  define_method(behaviour) do |via_entity,entity,options={}|
365
413
  run_callbacks behaviour.to_sym do
366
- "Patriarch::Services::#{behaviour.classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
414
+ "Patriarch::Services::#{behaviour.classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
367
415
  end
368
416
  end
369
417
 
@@ -371,48 +419,56 @@ module Patriarch
371
419
  # undo_behave
372
420
  define_method(Patriarch.undo(behaviour)) do |via_entity,entity,options={}|
373
421
  run_callbacks Patriarch.undo(behaviour).to_sym do
374
- "Patriarch::Services::#{Patriarch.undo(behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
422
+ "Patriarch::Services::#{Patriarch.undo(behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
375
423
  end
376
424
  end
377
425
 
378
426
  end # instance_eval
379
427
  end
380
428
 
429
+ # @param [String] behaviour
430
+ # @param [Hash] options
431
+ # Registers at a class level a declaration of bipartite behaviour so that it can be used
432
+ # to check protagonist types when calling behaviours
381
433
  def register_bipartite_behaviour(behaviour,options)
434
+ behaviour = behaviour.underscore.to_sym
435
+
382
436
  self.patriarch_behaviours ||= { }
383
437
  #self.patriarch_behaviours[behaviour.underscore.to_sym] ||= { :on => [], :by =>[] }
384
- self.patriarch_behaviours[behaviour.underscore.to_sym] ||= { }
385
- self.patriarch_behaviours[behaviour.underscore.to_sym][:on] ||= []
386
- self.patriarch_behaviours[behaviour.underscore.to_sym][:by] ||= []
387
-
388
- if options[:on]
389
- self.patriarch_behaviours[behaviour.underscore.to_sym][:on] << options[:on]
390
- self.patriarch_behaviours[behaviour.underscore.to_sym][:on].uniq!
391
- self.patriarch_behaviours[behaviour.underscore.to_sym][:on].flatten!
438
+ self.patriarch_behaviours[behaviour] ||= { }
439
+ self.patriarch_behaviours[behaviour][:on] ||= []
440
+ self.patriarch_behaviours[behaviour][:by] ||= []
441
+
442
+ if options[:on]
443
+ self.patriarch_behaviours[behaviour][:on] << options[:on]
444
+ self.patriarch_behaviours[behaviour][:on].uniq!
445
+ self.patriarch_behaviours[behaviour][:on].flatten!
392
446
  elsif options[:by]
393
- self.patriarch_behaviours[behaviour.underscore.to_sym][:by] << options[:by]
394
- self.patriarch_behaviours[behaviour.underscore.to_sym][:by].uniq!
395
- self.patriarch_behaviours[behaviour.underscore.to_sym][:by].flatten!
396
- end
447
+ self.patriarch_behaviours[behaviour][:by] << options[:by]
448
+ self.patriarch_behaviours[behaviour][:by].uniq!
449
+ self.patriarch_behaviours[behaviour][:by].flatten!
450
+ end
397
451
  end
398
452
 
453
+ # @param [String] behaviour
454
+ # @param [Hash] options
455
+ # Registers at a class level a declaration of tripartite behaviour so that it can be used
456
+ # to check protagonist types when calling behaviours
399
457
  def register_tripartite_behaviour(behaviour,options)
400
- # TODO disjonction register tripartite
401
- # register the behaviour we just added
402
458
  behaviour = behaviour.underscore.to_sym
403
459
 
404
460
  self.patriarch_behaviours ||= { }
405
- self.patriarch_behaviours[behaviour] ||= {}
406
-
461
+ self.patriarch_behaviours[behaviour] ||= {}
462
+
407
463
  if options[:via]
408
464
  #self.patriarch_behaviours[behaviour.underscore.to_sym] ||= { :on => [], :by => [], :via => [] }
409
465
  self.patriarch_behaviours[behaviour][:on] ||= []
410
- self.patriarch_behaviours[behaviour][:by] ||= []
411
- self.patriarch_behaviours[behaviour][:via] ||= []
466
+ self.patriarch_behaviours[behaviour][:by] ||= []
467
+ self.patriarch_behaviours[behaviour][:via] ||= []
412
468
  elsif options[:medium_between]
413
469
  #self.patriarch_behaviours[behaviour.underscore.to_sym] ||= { :medium_between => [] }
414
- self.patriarch_behaviours[behaviour][:medium_between] ||= []
415
- end
470
+ self.patriarch_behaviours[behaviour][:medium_between] ||= []
471
+ end
416
472
 
417
473
  if options[:via]
418
474
  if options[:on]
@@ -426,35 +482,38 @@ module Patriarch
426
482
  end
427
483
  self.patriarch_behaviours[behaviour][:via] << options[:via]
428
484
  self.patriarch_behaviours[behaviour][:via].uniq!
429
- self.patriarch_behaviours[behaviour][:via].flatten!
485
+ self.patriarch_behaviours[behaviour][:via].flatten!
430
486
  elsif options[:medium_between]
431
487
  self.patriarch_behaviours[behaviour][:medium_between] << options[:medium_between]
432
488
  self.patriarch_behaviours[behaviour][:medium_between].uniq!
433
489
  self.patriarch_behaviours[behaviour][:medium_between]
434
- end
490
+ end
435
491
  end
436
492
 
493
+ # @param [Symbol] behaviour the tripartite behaviour we want to add to our model
494
+ # @param [Object] options information useful to determine if supplementary functionality such as aliases or custom
495
+ # tool method shall be implemented
437
496
  def add_tripartite_behaviour(behaviour,options)
438
497
  behaviour = behaviour.to_s.underscore
439
498
 
440
- check_add_behaviour_syntax(behaviour,options)
499
+ check_add_behaviour_syntax(options)
441
500
 
442
501
  methods_mod = Module.new do; end
443
502
 
444
503
  # Target on Actor cases
445
504
  if options[:via]
446
505
  # Actor case
447
- if options[:on]
448
- add_functionnalities_for_tripartite(methods_mod,behaviour,options)
449
- add_aliases_for_functionnalities(methods_mod,behaviour,options)
506
+ if options[:on]
507
+ add_functionality_for_tripartite(methods_mod,behaviour)
508
+ add_aliases_for_functionality(methods_mod,behaviour,options)
450
509
  acted_on_model_list = Array(options[:on])
451
510
  via_model_list = Array(options[:via])
452
- Patriarch::Behaviours.complete_custom_active_module_tripartite(methods_mod,behaviour,acted_on_model_list,via_model_list,options)
511
+ Patriarch::Behaviours.complete_custom_active_module_tripartite(methods_mod,behaviour,acted_on_model_list,via_model_list,options)
453
512
  #Target case
454
513
  elsif options[:by]
455
- targetted_by_model_list = Array(options[:by])
456
- via_model_list = Array(options[:via])
457
- Patriarch::Behaviours.complete_custom_passive_module_tripartite(methods_mod,behaviour,targetted_by_model_list,via_model_list,options)
514
+ targeted_by_model_list = Array(options[:by])
515
+ via_model_list = Array(options[:via])
516
+ Patriarch::Behaviours.complete_custom_passive_module_tripartite(methods_mod,behaviour,targeted_by_model_list,via_model_list,options)
458
517
  end
459
518
  # Medium case
460
519
  elsif options[:medium_between]
@@ -463,35 +522,38 @@ module Patriarch
463
522
 
464
523
  # Finally ...
465
524
  include methods_mod
466
- # include in which we can overwite the methods of the previous custom module included there ...
525
+ # include in which we can override the methods of the previous custom module included there ...
467
526
  include "Patriarch::Behaviours::#{behaviour.classify}::ToolsMethods".constantize
468
527
  # register behaviour we just added
469
- register_tripartite_behaviour(behaviour,options)
528
+ register_tripartite_behaviour(behaviour,options)
470
529
  end
471
530
 
531
+ # @param [Symbol] behaviour the bipartite behaviour we want to add to our model
532
+ # @param [Object] options information useful to determine if supplementary functionality such as aliases or custom
533
+ # tool method shall be implemented
472
534
  def add_bipartite_behaviour(behaviour,options)
473
- # add_behaviour :like, :by => bla, :on => [:item,:user] || :community, :as => :aimer, :reverse_as => :haïr ...
474
-
535
+ # add_behaviour :like, :by => bla, :on => [:item,:user] || :community, :as => :love, :reverse_as => :hate ...
536
+
475
537
  behaviour = behaviour.to_s.underscore
476
538
 
477
- check_add_behaviour_syntax(behaviour,options)
539
+ check_add_behaviour_syntax(options)
478
540
 
479
541
  methods_mod = Module.new do; end
480
542
 
481
- if options[:on]
543
+ if options[:on]
482
544
  # Adds active methods and defines the hook to set callbacks on them
483
- add_functionnalities_for_bipartite(methods_mod,behaviour,options)
484
- add_aliases_for_functionnalities(methods_mod,behaviour,options)
545
+ add_functionality_for_bipartite(methods_mod,behaviour)
546
+ add_aliases_for_functionality(methods_mod,behaviour,options)
485
547
  acted_on_model_list = Array(options[:on])
486
- Patriarch::Behaviours.complete_custom_active_module_bipartite(methods_mod,behaviour,acted_on_model_list,options)
548
+ Patriarch::Behaviours.complete_custom_active_module_bipartite(methods_mod,behaviour,acted_on_model_list,options)
487
549
  else
488
- targetted_by_model_list = Array(options[:by])
489
- Patriarch::Behaviours.complete_custom_passive_module_bipartite(methods_mod,behaviour,targetted_by_model_list,options)
550
+ targeted_by_model_list = Array(options[:by])
551
+ Patriarch::Behaviours.complete_custom_passive_module_bipartite(methods_mod,behaviour,targeted_by_model_list,options)
490
552
  end
491
553
 
492
554
  # Finally includes the custom module
493
555
  include methods_mod
494
- # include in which we can overwite the methods of the previous custom module included there ...
556
+ # include in which we can override the methods of the previous custom module included there ...
495
557
  include "Patriarch::Behaviours::#{behaviour.classify}::ToolsMethods".constantize
496
558
  # register the behaviour we just added
497
559
  register_bipartite_behaviour(behaviour,options)