patriarch 0.0.4 → 0.1.0

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.
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