patriarch 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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)