patriarch 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +1 -0
  2. data/README.md +43 -3
  3. data/lib/generators/patriarch/behaviour_generator.rb +117 -0
  4. data/lib/generators/patriarch/install_generator.rb +27 -10
  5. data/lib/generators/patriarch/templates/after_manager_service.rb +2 -1
  6. data/lib/generators/patriarch/templates/authorization_service.rb +1 -1
  7. data/lib/generators/patriarch/templates/before_manager_service.rb +2 -1
  8. data/lib/generators/patriarch/templates/before_service.rb +1 -1
  9. data/lib/generators/patriarch/templates/empty_behaviours_declaration.rb +2 -0
  10. data/lib/generators/patriarch/templates/empty_services_declaration.rb +2 -0
  11. data/lib/generators/patriarch/templates/manager_service-tripartite.rb +36 -0
  12. data/lib/generators/patriarch/templates/manager_service.rb +15 -8
  13. data/lib/generators/patriarch/templates/service-tripartite.rb +5 -0
  14. data/lib/generators/patriarch/templates/service.rb +1 -1
  15. data/lib/generators/patriarch/templates/tools_methods.rb +2 -1
  16. data/lib/generators/patriarch/templates/undo_service-tripartite.rb +5 -0
  17. data/lib/patriarch/behaviours.rb +170 -13
  18. data/lib/patriarch/dao_services/bipartite_relationship_builder_service.rb +0 -4
  19. data/lib/patriarch/dao_services/redis_mapper_service.rb +45 -8
  20. data/lib/patriarch/dao_services/retriever_service.rb +26 -4
  21. data/lib/patriarch/dao_services/tripartite_relationship_builder_service.rb +51 -0
  22. data/lib/patriarch/manager_service.rb +0 -9
  23. data/lib/patriarch/tool_services/redis_cleaner_service.rb +64 -0
  24. data/lib/patriarch/tool_services/redis_extractor_service.rb +33 -4
  25. data/lib/patriarch/transaction.rb +9 -4
  26. data/lib/patriarch/transaction_services/transaction_manager_service.rb +9 -0
  27. data/lib/patriarch/transaction_step.rb +10 -2
  28. data/lib/patriarch/version.rb +1 -1
  29. data/lib/patriarch.rb +11 -5
  30. data/patriarch.gemspec +2 -4
  31. data/spec/lib/patriarch/behaviours_spec.rb +233 -0
  32. data/spec/lib/patriarch/{bipartite_relationship_builder_spec.rb → dao_services/bipartite_relationship_builder_spec.rb} +19 -23
  33. data/spec/lib/patriarch/dao_services/tripartite_relationship_builder_spec.rb +49 -0
  34. data/spec/lib/patriarch/redis_mapper_service_spec.rb +46 -14
  35. data/spec/lib/patriarch/retriever_service_spec.rb +27 -8
  36. data/spec/lib/patriarch/tool_services/redis_cleaner_service_spec.rb +48 -0
  37. data/spec/lib/patriarch/transaction_spec.rb +13 -0
  38. data/spec/lib/patriarch/transaction_step_spec.rb +29 -0
  39. data/spec/spec_helper.rb +45 -11
  40. metadata +30 -53
  41. data/lib/generators/patriarch/patriarch_generator.rb +0 -114
  42. data/lib/patriarch/dao_services/.DAOinstancier.rb.swp +0 -0
  43. data/lib/patriarch/tool_services/redis_cleaner.rb +0 -47
  44. data/spec/lib/patriarch/add_behaviour_spec.rb +0 -59
  45. data/spec/lib/patriarch/like_service_spec.rb +0 -19
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ redis_reclip_cleaner.rb
1
2
  spec/dummy
2
3
  *.gem
3
4
  *.rbc
data/README.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # Patriarch
2
2
 
3
- TODO: Write a gem description
3
+ N-N tables are often a pain to deal with in SQL especially when you want to
4
+ - have a lot of different N-N relations
5
+ - have a fast API with a lot of calls
6
+
7
+ A solution is to use Redis to store the results of the joins in a simple way. For instance
8
+ a user likes many items → Let's store the items ids liked into a redis_key
9
+ an item is liked by many users → reverse logic
10
+
11
+
12
+ The complexity also increases as you add callbacks. Let's say you provide users with the
13
+ behaviour "like" and "subscribe". You may want to trigger "subscribe" right after "like" was performed
14
+ and keep track of all this in one transaction.
15
+
16
+
17
+ Patriarch allows you to handle all of this in a matter of seconds and
18
+ - gathers all of the redis calls in one transaction object that stores a redis queue ready to be processed
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
21
+
22
+ # What Patriarch is not
23
+
24
+ Patriarch is not a replacement for SQL
4
25
 
5
26
  ## Installation
6
27
 
@@ -10,7 +31,7 @@ Add this line to your application's Gemfile:
10
31
 
11
32
  And then execute:
12
33
 
13
- $ bundle
34
+ $ bundle update
14
35
 
15
36
  Or install it yourself as:
16
37
 
@@ -18,7 +39,26 @@ Or install it yourself as:
18
39
 
19
40
  ## Usage
20
41
 
21
- TODO: Write usage instructions here
42
+ First include it into your model:
43
+
44
+ class User < ActiveRecord::Base
45
+ include Patriarch::Behaviours
46
+ end
47
+
48
+ ### Initializing files for a behaviour
49
+
50
+ Just type in:
51
+
52
+ rails generate patriach:behaviour BEHAVIOUR
53
+
54
+ ### Bipartite relations
55
+
56
+ Add a behaviour in a simple way and write this just below the include:
57
+
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
60
+
61
+ It works like belongs_to and has_many helpers of active record, you need to include declarations in both models
22
62
 
23
63
  ## Contributing
24
64
 
@@ -0,0 +1,117 @@
1
+ require "rails/generators"
2
+ require "redis/objects/sorted_sets"
3
+
4
+ module Patriarch
5
+ module Generators
6
+ class BehaviourGenerator < Rails::Generators::Base
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ desc "Generate files needed to implement the BEHAVIOUR you specified. Don't forget to add declarations into models"
10
+
11
+ argument :behaviour, :type => :string
12
+ argument :behaviour_type, :type => :string, :optional => true, :default => "bipartite"
13
+
14
+ public
15
+
16
+ def fail_if_bad_syntax
17
+ if behaviour_type != "bipartite" && behaviour_type != "tripartite"
18
+ # Pleasedon't, qualify an exception here
19
+ raise Exception, "bad syntax behaviour_type must be bipartite or tripartite"
20
+ end
21
+ end
22
+
23
+ def init_directories_for_behaviour
24
+ empty_directory "lib/patriarch/services/#{behaviour.underscore.downcase}"
25
+ empty_directory "lib/patriarch/services/#{Patriarch.undo(behaviour.underscore.downcase)}"
26
+ end
27
+
28
+ def customize_basic_files
29
+ template "empty_behaviour_module.rb", "lib/patriarch/behaviours/#{behaviour.underscore.downcase}.rb"
30
+ template "empty_service_module.rb", "lib/patriarch/services/#{behaviour.underscore.downcase}.rb"
31
+ template "tools_methods.rb", "lib/patriarch/behaviours/#{behaviour.underscore.downcase}/tools_methods.rb"
32
+ end
33
+
34
+ def ensure_autoload
35
+ # Autoload declaration insertion for services
36
+ insert_into_file "lib/patriarch/services.rb", :after => " module Services\n" do
37
+ " autoload :#{class_name}, 'patriarch/services/#{behaviour.underscore.downcase}.rb'\n"
38
+ end
39
+
40
+ insert_into_file "lib/patriarch/services.rb", :after => " module Services\n" do
41
+ " autoload :#{undo_class_name}, 'patriarch/services/#{behaviour.underscore.downcase}.rb'\n"
42
+ end
43
+
44
+ # Autoload declaration insertion for behaviours (no undo needed ...)
45
+ insert_into_file "lib/patriarch/behaviours.rb", :after => " module Behaviours\n" do
46
+ " autoload :#{class_name}, 'patriarch/behaviours/#{behaviour.underscore.downcase}.rb'\n"
47
+ end
48
+
49
+ # load File.expand_path('lib/patriarch/behaviours.rb', __FILE__)
50
+ end
51
+
52
+ def generate_services
53
+ create_services(behaviour,behaviour_type)
54
+ # implémenter un switch ici, plus zoli ...
55
+ self.class.send(:define_method,:class_name) do
56
+ Patriarch.undo(behaviour).classify
57
+ end
58
+ create_undo_services(behaviour,behaviour_type)
59
+ self.class.send(:define_method,:class_name) do
60
+ behaviour.classify
61
+ end
62
+ end
63
+
64
+ private
65
+ def class_name
66
+ behaviour.classify
67
+ end
68
+
69
+ def undo_class_name
70
+ Patriarch.undo(behaviour).classify
71
+ end
72
+
73
+ def create_services(behaviour,behaviour_type)
74
+ behaviour_str = behaviour.underscore.downcase
75
+ template "authorization_service.rb", "lib/patriarch/services/#{behaviour_str}/authorization_service.rb"
76
+
77
+ template "before_manager_service.rb", "lib/patriarch/services/#{behaviour_str}/before_manager_service.rb"
78
+ template "before_service.rb", "lib/patriarch/services/#{behaviour_str}/before_service.rb"
79
+
80
+
81
+
82
+ if behaviour_type == "tripartite"
83
+ template "service-tripartite.rb", "lib/patriarch/services/#{behaviour_str}/service.rb"
84
+ template "manager_service-tripartite.rb", "lib/patriarch/services/#{behaviour_str}/manager_service.rb"
85
+ elsif behaviour_type == "bipartite"
86
+ template "service.rb", "lib/patriarch/services/#{behaviour_str}/service.rb"
87
+ template "manager_service.rb", "lib/patriarch/services/#{behaviour_str}/manager_service.rb"
88
+ end
89
+
90
+ template "after_manager_service.rb", "lib/patriarch/services/#{behaviour_str}/after_manager_service.rb"
91
+ template "after_service.rb", "lib/patriarch/services/#{behaviour_str}/after_service.rb"
92
+ end
93
+
94
+ def create_undo_services(behaviour,behaviour_type)
95
+ undo_behaviour_str = Patriarch.undo(behaviour).underscore.downcase
96
+
97
+ template "authorization_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/authorization_service.rb"
98
+
99
+ template "before_manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/before_manager_service.rb"
100
+ template "before_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/before_service.rb"
101
+
102
+ # Please don't go another generator if necessary or do other functions
103
+ if behaviour_type == "tripartite"
104
+ template "undo_service-tripartite.rb", "lib/patriarch/services/#{undo_behaviour_str}/service.rb"
105
+ template "manager_service-tripartite.rb", "lib/patriarch/services/#{undo_behaviour_str}/manager_service.rb"
106
+ elsif behaviour_type == "bipartite"
107
+ template "undo_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/service.rb"
108
+ template "manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/manager_service.rb"
109
+ end
110
+
111
+ template "after_manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/after_manager_service.rb"
112
+ template "after_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/after_service.rb"
113
+ end
114
+
115
+ end # BehaviourGenerator
116
+ end # Generators
117
+ end # Patriarch
@@ -1,11 +1,28 @@
1
1
  require "rails/generators"
2
-
3
- class InstallGenerator < Rails::Generators::Base
4
- source_root File.expand_path('../templates', __FILE__)
5
-
6
- desc "Installs the gem"
7
-
8
- def init_directories
9
- copy_file "empty_behaviours_declaration.rb", "lib/patriarch/behaviours.rb"
10
- end
11
- end # InstallGenerator
2
+ module Patriarch
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ desc "Installs the gem"
8
+
9
+ def init_directories
10
+ copy_file "empty_behaviours_declaration.rb", "lib/patriarch/behaviours.rb"
11
+ end
12
+
13
+ def copy_compulsory_files
14
+ copy_file "empty_behaviours_declaration.rb", "lib/patriarch/behaviours.rb"
15
+ copy_file "empty_services_declaration.rb", "lib/patriarch/services.rb"
16
+ end
17
+
18
+ def build_initializer
19
+ create_file "config/initializers/patriarch.rb" do
20
+ "require 'patriarch'\n\n"+
21
+ "load File.expand_path('../../../lib/patriarch/behaviours.rb', __FILE__)\n" +
22
+ "load File.expand_path('../../../lib/patriarch/services.rb', __FILE__)"
23
+ end
24
+ end
25
+
26
+ end # InstallGenerator
27
+ end # Generators
28
+ end # Patriarch
@@ -1,6 +1,7 @@
1
1
  class Patriarch::Services::<%= class_name %>::AfterManagerService < Patriarch::ManagerService
2
2
  def resolve(transac)
3
- # Manages what happens after the service call
3
+ # Manages what happens after the service call, it allows you to be independant
4
+ # from active_models callback if you wish to do so
4
5
  Patriarch::Services::<%= class_name %>::AfterService.instance.call(transac) if true
5
6
  end
6
7
  end
@@ -1,6 +1,6 @@
1
1
  class Patriarch::Services::<%= class_name %>::AuthorizationService < Patriarch::AuthorizationService
2
2
  def grant?(transaction_item)
3
- # Instert authorization logic here ...
3
+ # Insert authorization logic here ...
4
4
  true
5
5
  end
6
6
  end
@@ -1,6 +1,7 @@
1
1
  class Patriarch::Services::<%= class_name %>::BeforeManagerService < Patriarch::ManagerService
2
2
  def resolve(transac)
3
- # Manages what happens after the service call
3
+ # Manages what happens after the service call, it allows you to be independant
4
+ # from active_models callback if you wish to do so
4
5
  Patriarch::Services::<%= class_name %>::BeforeService.instance.call(transac) if true
5
6
  end
6
7
  end
@@ -1,6 +1,6 @@
1
1
  class Patriarch::Services::<%= class_name %>::BeforeService < Patriarch::Service
2
2
  def call(transac,options={})
3
- # Add after service logic here and return the corresponding boolean
3
+ # Add after service logic here and return the boolean corresponding to success (or not)
4
4
  true
5
5
  end
6
6
  end
@@ -1,5 +1,7 @@
1
1
  module Patriarch
2
2
  module Behaviours
3
3
  # Autoloads declaration will be inserted here
4
+ # Do not modify indentation or anything else since it would mess up
5
+ # the generator when it tries to insert autoload declarations
4
6
  end
5
7
  end
@@ -1,5 +1,7 @@
1
1
  module Patriarch
2
2
  module Services
3
3
  # Autoloads declaration will be inserted here
4
+ # Do not modify indentation or anything else since it would mess up
5
+ # the generator when it tries to insert autoload declarations
4
6
  end
5
7
  end
@@ -0,0 +1,36 @@
1
+ class Patriarch::Services::<%= class_name %>::ManagerService < Patriarch::ManagerService
2
+ # Return true or false
3
+ def resolve(actor, target, medium, options={})
4
+
5
+ # Flag initialized only if first add_step
6
+ # It allows you to insert other behaviours into the callbacks without quitting the transaction
7
+ # A like of an user on an item can trigger a subscribe to an item for instance
8
+ # It means it will rollback if follow is not performed correctly
9
+
10
+ if options[:transac]
11
+ transac_first_step = false
12
+ else
13
+ transac_first_step = true
14
+ end
15
+
16
+ transac = options[:transac] || Patriarch::TransactionServices::TransactionManagerService.instance.new_transaction(:<%= class_name.underscore.to_sym %>)
17
+ transac.add_step(:<%= class_name.underscore.to_sym %>,actor,target,medium)
18
+
19
+ callbacks_were_completed = false
20
+
21
+ # Before logic not implemented yet here, to come in further releases
22
+ if Patriarch::Services::<%= class_name %>::AuthorizationService.instance.grant?(transac)
23
+ Patriarch::Services::<%= class_name %>::Service.instance.call(transac)
24
+ if Patriarch::Services::<%= class_name %>::AfterManagerService.instance.resolve(transac)
25
+ callbacks_were_completed = true
26
+ end
27
+ end
28
+
29
+ # In some cases we want to interrupt the transaction before it executes and just play with
30
+ # the transaction object
31
+ return transac if options[:stop_execution]
32
+
33
+ # If everything went smoothly
34
+ transac.execute if (transac_first_step && callbacks_were_completed)
35
+ end
36
+ end
@@ -1,29 +1,36 @@
1
1
  class Patriarch::Services::<%= class_name %>::ManagerService < Patriarch::ManagerService
2
2
  # Return true or false
3
- def resolve(actor, target, transac = nil)
3
+ def resolve(actor, target, options={})
4
4
 
5
- # On plante le "drapeau" transac_first_step pour signifier le début
6
- if transac
5
+ # Flag initialized only if first add_step
6
+ # It allows you to insert other behaviours into the callbacks without quitting the transaction
7
+ # A like of an user on an item can trigger a subscribe to an item for instance
8
+ # It means it will rollback if follow is not performed correctly
9
+
10
+ if options[:transac]
7
11
  transac_first_step = false
8
12
  else
9
13
  transac_first_step = true
10
14
  end
11
15
 
12
- transac ||= Patriarch::TransactionServices::TransactionManagerService.instance.new_transaction(:<%= class_name.underscore.to_sym %>)
13
- # Do not listen Loïc anymore ... DO NOT MOVE, LikeAuthorizationService needs us to build
14
- # a transaction step
16
+ transac = options[:transac] || Patriarch::TransactionServices::TransactionManagerService.instance.new_transaction(:<%= class_name.underscore.to_sym %>)
15
17
  transac.add_step(:<%= class_name.underscore.to_sym %>,actor,target)
16
18
 
17
19
  callbacks_were_completed = false
18
20
 
21
+ # Before logic not implemented yet here, to come in further releases
19
22
  if Patriarch::Services::<%= class_name %>::AuthorizationService.instance.grant?(transac)
20
- Patriarch::Services::<%= class_name %>::Service.instance.call(transac)
23
+ Patriarch::Services::<%= class_name %>::Service.instance.call(transac)
21
24
  if Patriarch::Services::<%= class_name %>::AfterManagerService.instance.resolve(transac)
22
25
  callbacks_were_completed = true
23
26
  end
24
27
  end
25
28
 
26
- # Si tout s'est bien passé et que les callbacks ont été exécutés
29
+ # In some cases we want to interrupt the transaction before it executes and just play with
30
+ # the transaction object
31
+ return transac if options[:stop_execution]
32
+
33
+ # If everything went smoothly
27
34
  transac.execute if (transac_first_step && callbacks_were_completed)
28
35
  end
29
36
  end
@@ -0,0 +1,5 @@
1
+ class Patriarch::Services::<%= class_name %>::Service < Patriarch::Service
2
+ def call(transaction_item)
3
+ Patriarch::DAOServices::TripartiteRelationshipBuilderService.instance.create(transaction_item)
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  class Patriarch::Services::<%= class_name %>::Service < Patriarch::Service
2
2
  def call(transaction_item)
3
- Patriarch::DAOServices::BipartiteRelationshipBuilderService.instance.create(transaction_item)
3
+ Patriarch::DAOServices::BipartiteRelationshipBuilderService.instance.create(transaction_item)
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
1
  module Patriarch::Behaviours::<%= class_name %>::ToolsMethods
2
- # You can override tool methods here
2
+ # You can override tool methods here or add your own, this module is included by default into
3
+ # models having behaviour <%= class_name %>
3
4
  end
@@ -0,0 +1,5 @@
1
+ class Patriarch::Services::<%= class_name %>::Service < Patriarch::Service
2
+ def call(transaction_item)
3
+ Patriarch::DAOServices::TripartiteRelationshipBuilderService.instance.destroy(transaction_item)
4
+ end
5
+ end
@@ -8,7 +8,7 @@ module Patriarch
8
8
 
9
9
  class << self
10
10
 
11
- def complete_custom_active_module(module_to_complete,relation_type,acted_on_model_list)
11
+ def complete_custom_active_module_bipartite(module_to_complete,relation_type,acted_on_model_list)
12
12
  module_to_complete.class_eval do
13
13
 
14
14
  acted_on_model_list.each do |acted_on_model|
@@ -33,7 +33,7 @@ module Patriarch
33
33
  end
34
34
  end
35
35
 
36
- def complete_custom_passive_module(module_to_complete,relation_type,targetted_by_model_list)
36
+ def complete_custom_passive_module_bipartite(module_to_complete,relation_type,targetted_by_model_list)
37
37
  module_to_complete.class_eval do
38
38
 
39
39
  targetted_by_model_list.each do |targetted_by_model|
@@ -54,15 +54,80 @@ module Patriarch
54
54
  define_method(raw_tool_method_name) do |options={}|
55
55
  Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
56
56
  end
57
-
57
+
58
58
  end
59
+
59
60
  end
60
61
  end
61
62
 
63
+
64
+ def complete_custom_active_module_tripartite(module_to_complete,relation_type,acted_on_model_list,via_model_list)
65
+ module_to_complete.class_eval do
66
+
67
+ acted_on_model_list.each do |acted_on_model|
68
+ via_model_list.each do |via_model|
69
+
70
+ # Compute names based on conventions
71
+ classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{relation_type.to_s}_via_#{via_model.to_s.tableize}"
72
+ raw_tool_method_name = classic_tool_method_name + "_ids"
73
+ redis_key = "patriarch_" + classic_tool_method_name
74
+
75
+ # Define methods with the pattern : items_i_like that returns models and items_i_like_ids that return
76
+ # Redis key has the same radical as the method in class by convention (but has a patriarch_ prefix to avoid collisions with other gems)
77
+ define_method(classic_tool_method_name) do |options={}|
78
+ Patriarch::ToolServices::RedisExtractorService.instance.
79
+ get_models_from_ids(self,acted_on_model.to_s.classify.constantize,raw_tool_method_name,options.merge({:tripartite => true}))
80
+ end
81
+
82
+ define_method(raw_tool_method_name) do |options={}|
83
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true, :protagonist_type => :actor}))
84
+ # triplet is by convention [actor,target,medium]
85
+ # TODO Marshallize true items instead of some crappy convention ...
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+
94
+ def complete_custom_passive_module_tripartite(module_to_complete,relation_type,targetted_by_model_list,via_model_list)
95
+ module_to_complete.class_eval do
96
+
97
+ targetted_by_model_list.each do |targetted_by_model|
98
+ via_model_list.each do |via_model|
99
+ # Compute names based on conventions
100
+ progressive_present_relation_type = (Verbs::Conjugator.conjugate relation_type.to_sym, :aspect => :progressive).split(/ /).last
101
+ classic_tool_method_name = "#{targetted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me_via_#{via_model.to_s.tableize}"
102
+ raw_tool_method_name = classic_tool_method_name + "_ids"
103
+ redis_key = "patriarch_" + classic_tool_method_name
104
+
105
+ # Define methods with the pattern : items_i_like that returns models and items_i_like_ids that return
106
+ # Redis key has the same radical as the method in class by convention (but has a patriarch_ prefix to avoid collisions with other gems)
107
+ define_method(classic_tool_method_name) do |options={}|
108
+ Patriarch::ToolServices::RedisExtractorService.instance.
109
+ get_models_from_ids(self,targetted_by_model.to_s.classify.constantize,raw_tool_method_name,options.merge({:tripartite => true}))
110
+ end
111
+
112
+ define_method(raw_tool_method_name) do |options={}|
113
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options.merge({:tripartite => true , :protagonist_type => :target}))
114
+
115
+ # triplet is by convention [actor,target,medium]
116
+ # TODO Marshallize true items instead of some crappy convention ...
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+
62
124
  def included(klass)
63
125
  klass.extend ClassMethods
64
126
  klass.extend ActiveModel::Callbacks
65
127
  class << klass;
128
+ # not sure if should add an alias like behaviours here
129
+ # might override other behaviour methods from other namespaces
130
+ # but patriarch is damn heavy here
66
131
  attr_accessor :patriarch_behaviours
67
132
  end
68
133
  end
@@ -71,17 +136,97 @@ module Patriarch
71
136
  module ClassMethods
72
137
 
73
138
  def add_behaviour(behaviour,options)
139
+ if options[:medium_between] || options[:via]
140
+ add_tripartite_behaviour(behaviour,options)
141
+ elsif options[:on] || options[:by]
142
+ add_bipartite_behaviour(behaviour,options)
143
+ else
144
+ raise AddBehaviourSyntaxError, "syntax is wrong, declaration does not suplly enough arguments declared with right options"
145
+ end
146
+ end
147
+
148
+ def add_tripartite_behaviour(behaviour,options)
149
+ behaviour = behaviour.to_s.underscore
150
+
151
+ # Xor on options :medium_between and :via, disparate cases ...
152
+ unless options[:medium_between].nil? ^ options[:via].nil?
153
+ raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
154
+ end
155
+
156
+ if options[:via]
157
+ unless options[:by].nil? ^ options[:on].nil?
158
+ raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
159
+ end
160
+ else
161
+ medium_between_option = options[:medium_between]
162
+ unless medium_between_option.is_a?(Array) && medium_between_option.size == 2
163
+ raise AddBehaviourSyntaxError, "syntax with medium medium_between requires that you provide an array of two symbols/strings"
164
+ end
165
+ end
166
+
167
+
168
+ methods_mod = Module.new do; end
169
+ methods_mod.const_set(:Behaviour,behaviour)
170
+
171
+ # Target on Actor cases
172
+ if options[:via]
173
+ # Actor case
174
+ if options[:on]
175
+ methods_mod.instance_eval do
176
+ # Defines the hook thing ...
177
+ # TODO
178
+ def self.included(klass)
179
+ klass.extend ActiveModel::Callbacks
180
+ klass.send(:define_model_callbacks, self.const_get(:Behaviour).to_sym, Patriarch.undo(self.const_get(:Behaviour)).to_sym)
181
+ end
182
+
183
+ # behave
184
+ define_method(methods_mod.const_get(:Behaviour).to_sym) do |entity,via_entity,options={}|
185
+ run_callbacks methods_mod.const_get(:Behaviour).to_sym do
186
+ "Patriarch::Services::#{methods_mod.const_get(:Behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
187
+ end
188
+ end
189
+
190
+ # undo_behave
191
+ define_method(Patriarch.undo(methods_mod.const_get(:Behaviour)).to_sym) do |entity,via_entity,options={}|
192
+ run_callbacks Patriarch.undo(methods_mod.const_get(:Behaviour)).to_sym do
193
+ "Patriarch::Services::#{(Patriarch.undo(methods_mod.const_get(:Behaviour))).classify}::ManagerService".constantize.instance.resolve(self,entity,via_entity,options)
194
+ end
195
+ end
196
+ end
197
+
198
+ acted_on_model_list = Array(options[:on])
199
+ via_model_list = Array(options[:via])
200
+ Patriarch::Behaviours.complete_custom_active_module_tripartite(methods_mod,behaviour,acted_on_model_list,via_model_list)
201
+ #Target case
202
+ elsif options[:by]
203
+ targetted_by_model_list = Array(options[:by])
204
+ via_model_list = Array(options[:via])
205
+ Patriarch::Behaviours.complete_custom_passive_module_tripartite(methods_mod,behaviour,targetted_by_model_list,via_model_list)
206
+ end
207
+ # Medium case
208
+ elsif options[:medium_between]
209
+ # Define tool_methods_here ...
210
+ end
211
+
212
+ # Finally ...
213
+ self.send(:include,methods_mod)
214
+ # include in which we can overwite the methods of the previous custom module included there ...
215
+ self.send(:include,"Patriarch::Behaviours::#{behaviour.classify}::ToolsMethods".constantize)
216
+
217
+ end
218
+
219
+ def add_bipartite_behaviour(behaviour,options)
74
220
  # add_behaviour :like, :by => bla, :on => [:item,:user] || :community, :as => :aimer, :reverse_as => :haïr ...
75
221
 
76
- behaviour = behaviour.to_s.downcase
222
+ behaviour = behaviour.to_s.underscore
77
223
 
78
- # Ou exclusif sur les options on et by
224
+ # Xor on options :on and :by, is what is needed, raise an exception otherwise
79
225
  unless options[:by].nil? ^ options[:on].nil?
80
226
  raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
81
227
  end
82
228
 
83
- methods_mod = Module.new do;
84
- end
229
+ methods_mod = Module.new do; end
85
230
  methods_mod.const_set(:Behaviour,behaviour)
86
231
 
87
232
  if options[:on]
@@ -98,17 +243,19 @@ module Patriarch
98
243
  # like
99
244
  define_method(methods_mod.const_get(:Behaviour).to_sym) do |entity,options={}|
100
245
  run_callbacks methods_mod.const_get(:Behaviour).to_sym do
101
- "Patriarch::Services::#{methods_mod.const_get(:Behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity)
246
+ "Patriarch::Services::#{methods_mod.const_get(:Behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity,options)
102
247
  end
103
248
  end
104
249
 
105
250
  # undo_like
106
251
  define_method(Patriarch.undo(methods_mod.const_get(:Behaviour)).to_sym) do |entity,options={}|
107
252
  run_callbacks Patriarch.undo(methods_mod.const_get(:Behaviour)).to_sym do
108
- "Patriarch::Services::#{(Patriarch.undo(methods_mod.const_get(:Behaviour))).classify}::ManagerService".constantize.instance.resolve(self,entity)
253
+ "Patriarch::Services::#{(Patriarch.undo(methods_mod.const_get(:Behaviour))).classify}::ManagerService".constantize.instance.resolve(self,entity,options)
109
254
  end
110
255
  end
111
256
 
257
+ # Not used / not tested, added this method "en passant"
258
+ # FIX requirements with users
112
259
  # likes?
113
260
  define_method((Verbs::Conjugator.conjugate methods_mod.const_get(:Behaviour).to_sym, :person => :third).split(/ /).last) do |entity|
114
261
  self.send("#{entity.class.name.tableize}_i_#{methods_mod.const_get(:Behaviour)}_ids").include? entity.id
@@ -129,10 +276,10 @@ module Patriarch
129
276
 
130
277
  if options[:on]
131
278
  acted_on_model_list = Array(options[:on])
132
- Patriarch::Behaviours.complete_custom_active_module(methods_mod,behaviour,acted_on_model_list)
279
+ Patriarch::Behaviours.complete_custom_active_module_bipartite(methods_mod,behaviour,acted_on_model_list)
133
280
  else
134
281
  targetted_by_model_list = Array(options[:by])
135
- Patriarch::Behaviours.complete_custom_passive_module(methods_mod,behaviour,targetted_by_model_list)
282
+ Patriarch::Behaviours.complete_custom_passive_module_bipartite(methods_mod,behaviour,targetted_by_model_list)
136
283
  end
137
284
 
138
285
  # Finally ...
@@ -141,8 +288,18 @@ module Patriarch
141
288
  self.send(:include,"Patriarch::Behaviours::#{behaviour.classify}::ToolsMethods".constantize)
142
289
 
143
290
  # register the behaviour we just added
144
- self.patriarch_behaviours ||= []
145
- self.patriarch_behaviours = self.patriarch_behaviours << behaviour.to_sym
291
+ # TODO disjonction bi/tripart
292
+ self.patriarch_behaviours ||= { }
293
+ self.patriarch_behaviours[behaviour.underscore.to_sym] ||= { :on => [], :by =>[] }
294
+ if options[:on]
295
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:on] << options[:on]
296
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:on].uniq!
297
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:on].flatten!
298
+ elsif options[:by]
299
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:by] << options[:by]
300
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:by].uniq!
301
+ self.patriarch_behaviours[behaviour.underscore.to_sym][:by].flatten!
302
+ end
146
303
  end
147
304
  end
148
305