patriarch 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/lib/generators/patriarch/USAGE +8 -0
  2. data/lib/generators/patriarch/patriarch_generator.rb +69 -0
  3. data/lib/generators/patriarch/templates/after_manager_service.rb +6 -0
  4. data/lib/generators/patriarch/templates/after_service.rb +6 -0
  5. data/lib/generators/patriarch/templates/authorization_service.rb +6 -0
  6. data/lib/generators/patriarch/templates/before_manager_service.rb +6 -0
  7. data/lib/generators/patriarch/templates/before_service.rb +6 -0
  8. data/lib/generators/patriarch/templates/empty_behaviour_module.rb +2 -0
  9. data/lib/generators/patriarch/templates/manager_service.rb +29 -0
  10. data/lib/generators/patriarch/templates/service.rb +5 -0
  11. data/lib/generators/patriarch/templates/tools_methods.rb +3 -0
  12. data/lib/generators/patriarch/templates/undo_service.rb +5 -0
  13. data/lib/patriarch/authorization_service.rb +10 -0
  14. data/lib/patriarch/behaviours.rb +160 -0
  15. data/lib/patriarch/dao_services/.DAOinstancier.rb.swp +0 -0
  16. data/lib/patriarch/dao_services/bipartite_relationship_builder_service.rb +43 -0
  17. data/lib/patriarch/dao_services/redis_mapper_service.rb +41 -0
  18. data/lib/patriarch/dao_services/retriever_service.rb +51 -0
  19. data/lib/patriarch/manager_service.rb +15 -0
  20. data/lib/patriarch/service.rb +5 -0
  21. data/lib/patriarch/tool_services/redis_cleaner.rb +47 -0
  22. data/lib/patriarch/tool_services/redis_extractor_service.rb +37 -0
  23. data/lib/patriarch/transaction.rb +68 -0
  24. data/lib/patriarch/transaction_services/transaction_manager_service.rb +13 -0
  25. data/lib/patriarch/transaction_step.rb +39 -0
  26. data/lib/patriarch/version.rb +1 -1
  27. data/patriarch.gemspec +5 -2
  28. data/spec/lib/patriarch/add_behaviour_spec.rb +72 -0
  29. data/spec/lib/patriarch/bipartite_relationship_builder_spec.rb +51 -0
  30. data/spec/lib/patriarch/clean_reclip_spec.rb +31 -0
  31. data/spec/lib/patriarch/like_service_spec.rb +18 -0
  32. data/spec/lib/patriarch/reclip_spec.rb +39 -0
  33. data/spec/lib/patriarch/redis_mapper_service_spec.rb +20 -0
  34. data/spec/lib/patriarch/retriever_service_spec.rb +37 -0
  35. metadata +65 -4
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate judge Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,69 @@
1
+ class PatriarchGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ desc "Generate files needed to implement the BEHAVIOUR you specified. Don't forget to add declarations into models"
5
+
6
+ argument :behaviour, :type => :string
7
+
8
+ public
9
+ def init_directories
10
+ empty_directory "lib/patriarch/services/#{behaviour.underscore.downcase}"
11
+ empty_directory "lib/patriarch/services/#{undo(behaviour.underscore.downcase)}"
12
+ template "empty_behaviour_module.rb", "lib/patriarch/services/#{behaviour.underscore.downcase}.rb"
13
+ template "tools_methods.rb", "lib/patriarch/behaviours/#{behaviour.underscore.downcase}/tools_methods.rb"
14
+ end
15
+
16
+ def generate_services
17
+ create_services(behaviour)
18
+ # implémenter un switch ici, plus zoli ...
19
+ self.class.send(:define_method,:class_name) do
20
+ undo(behaviour).classify
21
+ end
22
+ create_undo_services(behaviour)
23
+ end
24
+
25
+ private
26
+ def class_name
27
+ behaviour.classify
28
+ end
29
+
30
+ def create_services(behaviour)
31
+ behaviour_str = behaviour.underscore.downcase
32
+ template "authorization_service.rb", "lib/patriarch/services/#{behaviour_str}/authorization_service.rb"
33
+
34
+ template "before_manager_service.rb", "lib/patriarch/services/#{behaviour_str}/before_manager_service.rb"
35
+ template "before_service.rb", "lib/patriarch/services/#{behaviour_str}/before_service.rb"
36
+
37
+ template "manager_service.rb", "lib/patriarch/services/#{behaviour_str}/manager_service.rb"
38
+
39
+ template "service.rb", "lib/patriarch/services/#{behaviour_str}/service.rb"
40
+
41
+
42
+ template "after_manager_service.rb", "lib/patriarch/services/#{behaviour_str}/after_manager_service.rb"
43
+ template "after_service.rb", "lib/patriarch/services/#{behaviour_str}/after_service.rb"
44
+ end
45
+
46
+ def create_undo_services(behaviour)
47
+ undo_behaviour_str = undo(behaviour).underscore.downcase
48
+
49
+ template "authorization_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/authorization_service.rb"
50
+
51
+ template "before_manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/before_manager_service.rb"
52
+ template "before_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/before_service.rb"
53
+
54
+ template "manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/manager_service.rb"
55
+
56
+ template "undo_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/service.rb"
57
+
58
+ template "after_manager_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/after_manager_service.rb"
59
+ template "after_service.rb", "lib/patriarch/services/#{undo_behaviour_str}/after_service.rb"
60
+ end
61
+
62
+
63
+ def undo(str)
64
+ undo = "undo_"
65
+ undo << str
66
+ undo
67
+ end
68
+
69
+ end
@@ -0,0 +1,6 @@
1
+ class Patriarch::Services::<%= class_name %>::AfterManagerService < Patriarch::ManagerService
2
+ def resolve(transac)
3
+ # Manages what happens after the service call
4
+ Patriarch::Services::<%= class_name %>::AfterService.instance.call(transac) if true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class Patriarch::Services::<%= class_name %>::AfterService < Patriarch::Service
2
+ def call(transac,options={})
3
+ # Add after service logic here and return the corresponding boolean
4
+ true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class Patriarch::Services::<%= class_name %>::AuthorizationService < Patriarch::AuthorizationService
2
+ def grant?(transaction_item)
3
+ # Instert authorization logic here ...
4
+ true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class Patriarch::Services::<%= class_name %>::BeforeManagerService < Patriarch::ManagerService
2
+ def resolve(transac)
3
+ # Manages what happens after the service call
4
+ Patriarch::Services::<%= class_name %>::BeforeService.instance.call(transac) if true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class Patriarch::Services::<%= class_name %>::BeforeService < Patriarch::Service
2
+ def call(transac,options={})
3
+ # Add after service logic here and return the corresponding boolean
4
+ true
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ module Patriarch::Services::<%= class_name %>
2
+ end
@@ -0,0 +1,29 @@
1
+ class Patriarch::Services::<%= class_name %>::ManagerService < Patriarch::ManagerService
2
+ # Return true or false
3
+ def resolve(actor, target, transac = nil)
4
+
5
+ # On plante le "drapeau" transac_first_step pour signifier le début
6
+ if transac
7
+ transac_first_step = false
8
+ else
9
+ transac_first_step = true
10
+ end
11
+
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
15
+ transac.add_step(:<%= class_name.underscore.to_sym %>,actor,target)
16
+
17
+ callbacks_were_completed = false
18
+
19
+ if Patriarch::Services::<%= class_name %>::AuthorizationService.instance.grant?(transac)
20
+ Patriarch::Services::<%= class_name %>::Service.instance.call(transac)
21
+ if Patriarch::Services::<%= class_name %>::AfterManagerService.instance.resolve(transac)
22
+ callbacks_were_completed = true
23
+ end
24
+ end
25
+
26
+ # Si tout s'est bien passé et que les callbacks ont été exécutés
27
+ transac.execute if (transac_first_step && callbacks_were_completed)
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ class Patriarch::Services::<%= class_name %>::Service < Patriarch::Service
2
+ def call(transaction_item)
3
+ Patriarch::DAOServices::BipartiteRelationshipBuilderService.instance.create(transaction_item)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Patriarch::Behaviours::<%= class_name %>::ToolsMethods
2
+ # You can override tool methods here
3
+ end
@@ -0,0 +1,5 @@
1
+ class Patriarch::Services::<%= class_name %>::Service < Patriarch::Service
2
+ def call(transaction_item)
3
+ Patriarch::DAOServices::BipartiteRelationshipBuilderService.instance.destroy(transaction_item)
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'singleton'
2
+
3
+ class Patriarch::AuthorizationService
4
+ include Singleton
5
+
6
+ # override if necessary
7
+ def grant?(*args)
8
+ true
9
+ end
10
+ end
@@ -0,0 +1,160 @@
1
+ #require 'patriarch/behaviours/own'
2
+ #require 'patriarch/behaviours/subscribe'
3
+ #require 'patriarch/behaviours/tagging'
4
+ #require 'patriarch/behaviours/join'
5
+
6
+ module Patriarch
7
+ module Behaviours
8
+
9
+ class AddBehaviourSyntaxError < Exception ; end
10
+
11
+ class << self
12
+
13
+ def complete_custom_active_module(module_to_complete,relation_type,acted_on_model_list)
14
+ module_to_complete.class_eval do
15
+
16
+ acted_on_model_list.each do |acted_on_model|
17
+
18
+ # Compute names based on conventions
19
+ classic_tool_method_name = "#{acted_on_model.to_s.tableize}_i_#{relation_type.to_s}"
20
+ raw_tool_method_name = classic_tool_method_name + "_ids"
21
+ redis_key = "patriarch_" + classic_tool_method_name
22
+
23
+ # Define methods with the pattern : items_i_like that returns models and items_i_like_ids that return
24
+ # Redis key has the same radical as the method in class by convention (but has a patriarch_ prefix to avoid collisions with other gems)
25
+ define_method(classic_tool_method_name) do |options={}|
26
+ Patriarch::ToolServices::RedisExtractorService.instance.
27
+ get_models_from_ids(self,acted_on_model.to_s.classify.constantize,raw_tool_method_name,options)
28
+ end
29
+
30
+ define_method(raw_tool_method_name) do |options={}|
31
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ def complete_custom_passive_module(module_to_complete,relation_type,targetted_by_model_list)
39
+ module_to_complete.class_eval do
40
+
41
+ targetted_by_model_list.each do |targetted_by_model|
42
+
43
+ # Compute names based on conventions
44
+ progressive_present_relation_type = (Verbs::Conjugator.conjugate relation_type.to_sym, :aspect => :progressive).split(/ /).last
45
+ classic_tool_method_name = "#{targetted_by_model.to_s.tableize}_#{progressive_present_relation_type}_me"
46
+ raw_tool_method_name = classic_tool_method_name + "_ids"
47
+ redis_key = "patriarch_" + classic_tool_method_name
48
+
49
+ # Define methods with the pattern : items_i_like that returns models and items_i_like_ids that return
50
+ # Redis key has the same radical as the method in class by convention (but has a patriarch_ prefix to avoid collisions with other gems)
51
+ define_method(classic_tool_method_name) do |options={}|
52
+ Patriarch::ToolServices::RedisExtractorService.instance.
53
+ get_models_from_ids(self,targetted_by_model.to_s.classify.constantize,raw_tool_method_name,options)
54
+ end
55
+
56
+ define_method(raw_tool_method_name) do |options={}|
57
+ Patriarch::ToolServices::RedisExtractorService.instance.get_ids_from_sorted_set(self,redis_key,options)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+
64
+ def undo(str)
65
+ undo = "undo_"
66
+ undo << str
67
+ undo
68
+ end
69
+
70
+ def included(klass)
71
+ klass.extend ClassMethods
72
+ klass.extend ActiveModel::Callbacks
73
+ class << klass;
74
+ attr_accessor :patriarch_behaviours
75
+ end
76
+ end
77
+ end
78
+
79
+ module ClassMethods
80
+ def add_behaviours(*behaviours)
81
+ end
82
+
83
+ def add_behaviour(behaviour,options)
84
+ # add_behaviour :like, :by => bla, :on => [:item,:user] || :community, :as => :aimer, :reverse_as => :haïr ...
85
+
86
+ behaviour = behaviour.to_s.downcase
87
+
88
+ # Ou exclusif sur les options on et by
89
+ unless options[:by].nil? ^ options[:on].nil?
90
+ raise AddBehaviourSyntaxError, "you must not define a behaviour as active (using on) and passive at the same time (using by)"
91
+ end
92
+
93
+ methods_mod = Module.new do;
94
+ end
95
+ methods_mod.const_set(:Behaviour,behaviour)
96
+
97
+ if options[:on]
98
+ # Adds active methods and defines the hook to set callbacks on them
99
+ methods_mod.instance_eval do
100
+
101
+ # Defines the hook thing ...
102
+ # TODO
103
+ def self.included(klass)
104
+ klass.extend ActiveModel::Callbacks
105
+ klass.send(:define_model_callbacks, self.const_get(:Behaviour).to_sym, Patriarch::Behaviours.undo(self.const_get(:Behaviour)).to_sym)
106
+ end
107
+
108
+ # like
109
+ define_method(methods_mod.const_get(:Behaviour).to_sym) do |entity,options={}|
110
+ run_callbacks methods_mod.const_get(:Behaviour).to_sym do
111
+ "Patriarch::Services::#{methods_mod.const_get(:Behaviour).classify}::ManagerService".constantize.instance.resolve(self,entity)
112
+ end
113
+ end
114
+
115
+ # undo_like
116
+ define_method(Patriarch::Behaviours.undo(methods_mod.const_get(:Behaviour)).to_sym) do |entity,options={}|
117
+ run_callbacks Patriarch::Behaviours.undo(methods_mod.const_get(:Behaviour)).to_sym do
118
+ "Patriarch::Services::#{(Patriarch::Behaviours.undo(methods_mod.const_get(:Behaviour))).classify}::ManagerService".constantize.instance.resolve(self,entity)
119
+ end
120
+ end
121
+
122
+ # likes?
123
+ define_method((Verbs::Conjugator.conjugate methods_mod.const_get(:Behaviour).to_sym, :person => :third).split(/ /).last) do |entity|
124
+ self.send("#{entity.class.name.tableize}_i_#{methods_mod.const_get(:Behaviour)}_ids").include? entity.id
125
+ end
126
+ end
127
+ end
128
+
129
+ if options[:by]
130
+ methods_mod.instance_eval do
131
+ # reverse_of_likes?
132
+ # TODO find naming convention and build general ...
133
+ # define_method(:reverse_of_likes) do |entity|
134
+ # progressive_present_relation_type = (Verbs::Conjugator.conjugate methods_mod.const_get(:Behaviour).to_sym, :aspect => :progressive).split(/ /).last
135
+ # self.send("#{entity.class.name.tableize}_#{progressive_present_relation_type}_me_ids").include? entity.id
136
+ #end
137
+ end
138
+ end
139
+
140
+ if options[:on]
141
+ acted_on_model_list = Array(options[:on])
142
+ Patriarch::Behaviours.complete_custom_active_module(methods_mod,behaviour,acted_on_model_list)
143
+ else
144
+ targetted_by_model_list = Array(options[:by])
145
+ Patriarch::Behaviours.complete_custom_passive_module(methods_mod,behaviour,targetted_by_model_list)
146
+ end
147
+
148
+ # Finally ...
149
+ self.send(:include,methods_mod)
150
+ # include in which we can overwite the methods of the previous custom module included there ...
151
+ self.send(:include,"Patriarch::Behaviours::#{behaviour.classify}::ToolsMethods".constantize)
152
+
153
+ # register the behaviour we just added
154
+ self.patriarch_behaviours ||= []
155
+ self.patriarch_behaviours = self.patriarch_behaviours << behaviour.to_sym
156
+ end
157
+ end
158
+
159
+ end # Behaviours
160
+ end # Patriarch
@@ -0,0 +1,43 @@
1
+ class Patriarch::DAOServices::BipartiteRelationshipBuilderService < Patriarch::Service
2
+ def create(transaction_item)
3
+ # t => changer nom pour différencier de la date créée pour le Patriarch::Transaction
4
+ t = Time.now.to_f
5
+ dao_tab = Patriarch::DAOServices::RetrieverService.instance.call(transaction_item)
6
+
7
+ # Go hash plz
8
+ actor_dao = dao_tab[:actor]
9
+ target_dao = dao_tab[:target]
10
+
11
+ l = build_lambda_for_create(actor_dao,transaction_item.target_id,t)
12
+ ll = build_lambda_for_create(target_dao,transaction_item.actor_id,t)
13
+
14
+ # care about that, should be encapsulated into a beautiful add_to_queue method
15
+ transaction_item.add_to_queue l
16
+ transaction_item.add_to_queue ll
17
+ end
18
+
19
+ def destroy(transaction_item)
20
+ dao_tab = Patriarch::DAOServices::RetrieverService.instance.call(transaction_item)
21
+
22
+ # Go hash plz
23
+ actor_dao = dao_tab[:actor]
24
+ target_dao = dao_tab[:target]
25
+
26
+ l = lambda { actor_dao.delete transaction_item.target_id }
27
+ ll = lambda { target_dao.delete transaction_item.actor_id }
28
+
29
+ # care about that, should be encapsulated into a beautiful add_to_queue method
30
+ transaction_item.add_to_queue l
31
+ transaction_item.add_to_queue ll
32
+ end
33
+
34
+ protected
35
+
36
+ def build_lambda_for_create(dao,id,time)
37
+ if dao.is_a? Redis::SortedSet
38
+ return lambda { dao.add id, time }
39
+ else
40
+ return lambda { dao.add id }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ class Patriarch::DAOServices::RedisMapperService < Patriarch::Service
2
+ def call(transac,protagonist_type)
3
+
4
+ # Getting symbols here ...
5
+ relation_type = transac.relation_type
6
+ actor_type = transac.actor_type
7
+ target_type = transac.target_type
8
+
9
+ relation_type_str = sanitize_relation_type(relation_type.to_s)
10
+
11
+ if protagonist_type == :actor
12
+ redis_config_for_actor_on(target_type,relation_type_str)
13
+ else
14
+ redis_config_for_target_by(actor_type,relation_type_str)
15
+ end
16
+ end
17
+
18
+
19
+ def redis_config_for_actor_on(model_name,relation_type_str)
20
+ # example : items_i_like ...
21
+ {
22
+ :type => "sorted_set", :key => "patriarch_#{model_name.to_s.tableize}_i_#{relation_type_str}"
23
+ }
24
+ end
25
+
26
+ def redis_config_for_target_by(model_name,relation_type_str)
27
+ # example : items_liking_me ...
28
+ {
29
+ :type => "sorted_set", :key => "patriarch_#{model_name.to_s.tableize}_#{progressive_present(relation_type_str)}_me"
30
+ }
31
+ end
32
+
33
+ def progressive_present(behaviour_verb)
34
+ # like becomes => liking
35
+ (Verbs::Conjugator.conjugate behaviour_verb.to_sym, :aspect => :progressive).split(/ /).last
36
+ end
37
+
38
+ def sanitize_relation_type(relation_type)
39
+ relation_type.sub(/^undo_/,'')
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ class InvalidRedisTypeException < Exception
2
+ end
3
+
4
+ class Patriarch::DAOServices::RetrieverService < Patriarch::Service
5
+ # go hash
6
+ def call(transaction_item)
7
+ result = {}
8
+ result[:actor] = instantiate_DAO_for_actor(transaction_item)
9
+ result[:target] = instantiate_DAO_for_target(transaction_item)
10
+ result
11
+ end
12
+
13
+ protected
14
+
15
+ def get_DAO_info_for_actor(transaction_item)
16
+ Patriarch::DAOServices::RedisMapperService.instance.call(transaction_item,:actor)
17
+ end
18
+
19
+ def get_DAO_info_for_target(transaction_item)
20
+ Patriarch::DAOServices::RedisMapperService.instance.call(transaction_item,:target)
21
+ end
22
+
23
+ def instantiate_DAO_for_actor(transaction_item)
24
+ dao_info = get_DAO_info_for_actor(transaction_item)
25
+ actor_id = transaction_item.actor_id
26
+ actor_type = transaction_item.actor_type
27
+
28
+ if Redis.constants.map(&:to_s).include?("#{dao_info[:type].camelize}")
29
+ redis_dao_class = "Redis::#{dao_info[:type].camelize}".constantize
30
+ else
31
+ raise InvalidRedisTypeException
32
+ end
33
+
34
+ redis_dao_class.new("#{actor_type}:#{actor_id}:#{dao_info[:key]}")
35
+ end
36
+
37
+
38
+ def instantiate_DAO_for_target(transaction_item)
39
+ dao_info = get_DAO_info_for_target(transaction_item)
40
+ target_id = transaction_item.target_id
41
+ target_type = transaction_item.target_type
42
+
43
+ if Redis.constants.map(&:to_s).include?("#{dao_info[:type].camelize}")
44
+ redis_dao_class = "Redis::#{dao_info[:type].camelize}".constantize
45
+ else
46
+ raise InvalidRedisTypeException
47
+ end
48
+ redis_dao_class.new("#{target_type}:#{target_id}:#{dao_info[:key]}")
49
+ end
50
+ end
51
+
@@ -0,0 +1,15 @@
1
+ require 'singleton'
2
+
3
+ # Interface avec les couches les plus hautes
4
+ class Patriarch::ManagerService
5
+ include Singleton
6
+
7
+ # /!\ Changer l'architecture ici, l'idée est là
8
+ #def resolve(*args, options={})
9
+ # Traitement des options
10
+ # ...
11
+
12
+ # Traitement normal
13
+ # after_resolve(*args)
14
+ #end
15
+ end
@@ -0,0 +1,5 @@
1
+ require 'singleton'
2
+
3
+ class Judge::Service
4
+ include Singleton
5
+ end
@@ -0,0 +1,47 @@
1
+ class Patriarch::ToolServices::RedisExtractorService < Patriarch::Service
2
+ def clean_reclip_id(reclip_id)
3
+ # Go get the hash of this reclip to clean depencies
4
+ reclip_hash = Redis::HashKey.new("reclip:#{reclip_id}")
5
+
6
+ # Clean dependencies on the recliped_on entity, the recliper entity and the reclipped item
7
+
8
+ recliped_on_sorted_set = Redis::SortedSet.new("#{reclip_hash['recliped_on_type'].downcase}:#{reclip_hash['recliped_on_id']}:redis_reclipeds_on_me")
9
+ recliper_sorted_set = Redis::SortedSet.new("#{reclip_hash['recliper_type'].downcase}:#{reclip_hash['recliper_id']}:redis_reclipeds_by_me")
10
+ item_reclip_ids_sorted_set = Redis::SortedSet.new("item:#{reclip_hash['recliped_item_id']}:redis_reclips_of_me")
11
+
12
+ $redis.multi do
13
+ recliped_on_sorted_set.delete reclip_id # recliped_on is cleaned
14
+ recliper_sorted_set.delete reclip_id # recliper is cleaned
15
+ reclip_hash.del # hash is deleted properly
16
+ item_reclip_ids_sorted_set.delete reclip_id # item_key is cleaned from this reclip_id
17
+ end
18
+ end
19
+
20
+ def clean_reclip_for_item_before_delete(item)
21
+ item_reclip_ids = Redis::SortedSet.new("item:#{item.id}:redis_reclips_of_me")
22
+
23
+ # For each reclip involving our item
24
+ item_reclip_ids.members.each do |reclip_id|
25
+ clean_reclip_id(reclip_id)
26
+ end
27
+ end
28
+
29
+ def clean_reclip_for_group_before_delete(group)
30
+ # Goal here is to clean reclipeds_on_me dependencies and reclipeds_by_me before the group dies
31
+
32
+ # reclipeds_on_me
33
+ reclipeds_on_me_sorted_set = group.redis_reclipeds_on_me
34
+
35
+ reclipeds_on_me_sorted_set.members.each do |reclip_id|
36
+ clean_reclip_id(reclip_id)
37
+ end
38
+
39
+
40
+ # reclipeds_by_me
41
+ reclipeds_by_me_sorted_set = group.redis_reclipeds_by_me
42
+
43
+ reclipeds_by_me_sorted_set.members.each do |reclip_id|
44
+ clean_reclip_id(reclip_id)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ class Patriarch::ToolServices::RedisExtractorService < Patriarch::Service
2
+ def get_ids_from_sorted_set(calling_entity,redis_key,options={})
3
+ from = options[:from] || 0
4
+ limit = options[:limit] || :infinity
5
+
6
+ if limit == :infinity
7
+ to = -1
8
+ else
9
+ to = from + limit -1
10
+ end
11
+
12
+ dao = Redis::SortedSet.new("#{calling_entity.class.name.downcase}:#{calling_entity.id}:#{redis_key}")
13
+
14
+ if options[:with_scores]
15
+ # Do not use this anymore, go take the right redis objects not using this kind of methods ...
16
+ # DEGOLASSE
17
+ Redis::SortedSet.new
18
+ ids_with_scores_from_redis = dao.revrange(from,to,:with_scores => true) || []
19
+ ids_with_scores_from_redis.map{ |id,score| [id.to_i,score] }
20
+ else
21
+ ids_from_redis = dao.revrange(from,to) || []
22
+ ids_from_redis.map &:to_i
23
+ end
24
+ end
25
+
26
+ def get_models_from_ids(calling_entity,model,get_id_method,options={})
27
+ if options[:with_scores]
28
+ ids_with_scores_from_redis = calling_entity.send(get_id_method,options)
29
+ ids_with_scores_from_redis.map! do |id,score|
30
+ [model.find(id),score]
31
+ end
32
+ else
33
+ ids_from_redis = calling_entity.send(get_id_method,options)
34
+ model.find(ids_from_redis)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,68 @@
1
+ class Patriarch::Transaction
2
+ attr_reader :id, :steps, :current_step_number
3
+ attr_reader :type
4
+
5
+ forwarded_methods_syms = [:relation_type,:actor_type,:target_type,:actor_id,:target_id,
6
+ :context,:actor,:target]
7
+
8
+ # Forward methods that are not this transaction's job to step object
9
+ forwarded_methods_syms.each do |method_sym|
10
+ define_method(method_sym) do
11
+ current_step.send(method_sym)
12
+ end
13
+ end
14
+
15
+
16
+ # id => go $redis.incr
17
+ def initialize(type,id) #relation_type,actor,target,
18
+ @type = type
19
+ @id = id
20
+ @steps = []
21
+ @current_step_number = 0
22
+ end
23
+
24
+ # Initializes a new step and stores it in the steps array right away
25
+ # A step matches a "like", "follow", "own", etc.
26
+ # Register that we went a step further into the transaction processus
27
+ # Medium is nil by default (bipartite transactions does not require more than target/actor)
28
+ def add_step(relation_type,actor,target,medium=nil)
29
+ # Initializes a new step and stores it in the steps array right away
30
+ new_step = Patriarch::TransactionStep.new(relation_type,actor,target,medium)
31
+
32
+ # if initilization failed we should not move forward ...
33
+ raise PatriarchTransactionStepInstanciationException unless new_step
34
+
35
+ # Register that we went a step further into the transaction processus
36
+ @steps << new_step
37
+ @current_step_number += 1
38
+ end
39
+
40
+ # Executes the calls to redis in one block here.
41
+ def execute
42
+ $redis.multi do
43
+ steps.each do |step|
44
+ step.execute
45
+ end
46
+ end
47
+ end
48
+
49
+ def transaction_queue
50
+ transaction_queue = []
51
+ steps.each do |step|
52
+ transaction_queue.concat(step.queue)
53
+ end
54
+ transaction_queue
55
+ end
56
+
57
+ def add_to_queue(proc)
58
+ current_step.add_to_queue(proc)
59
+ end
60
+
61
+ alias :queue :transaction_queue
62
+
63
+ protected
64
+
65
+ def current_step
66
+ steps[current_step_number-1]
67
+ end
68
+ end
@@ -0,0 +1,13 @@
1
+ require 'singleton'
2
+
3
+ class PatriarchTransactionStepInstanciationException < Exception
4
+ end
5
+
6
+ class Patriarch::TransactionServices::TransactionManagerService < Patriarch::ManagerService
7
+ # Fill with logic to manage ressources with transactions ...
8
+ # Can registers transactions and so on ...
9
+ def new_transaction(type)
10
+ id = Time.now.to_f
11
+ Patriarch::Transaction.new(type,id)
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ class Patriarch::TransactionStep
2
+ attr_accessor :context, :queue
3
+
4
+ def initialize(relation_type,actor,target,medium)
5
+ @context = {
6
+ :relation_type => relation_type,
7
+ :actor_type => actor.class.name.underscore.to_sym,
8
+ :target_type => target.class.name.underscore.to_sym,
9
+ :actor_id => actor.id,
10
+ :target_id => target.id,
11
+ }
12
+ @queue = []
13
+ end
14
+
15
+ # defines access methods to context fields letting it be encapsulated nicely
16
+ [:relation_type,:actor_type,:target_type,:actor_id,:target_id].each do |context_key|
17
+ define_method(context_key) do
18
+ context[context_key]
19
+ end
20
+ end
21
+
22
+ def actor
23
+ actor_type.to_s.camelize.constantize.find actor_id
24
+ end
25
+
26
+ def target
27
+ target_type.to_s.camelize.constantize.find target_id
28
+ end
29
+
30
+ def execute
31
+ queue.each do |redis_instruction|
32
+ redis_instruction.call
33
+ end
34
+ end
35
+
36
+ def add_to_queue(proc)
37
+ queue << proc
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module Patriarch
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/patriarch.gemspec CHANGED
@@ -6,12 +6,15 @@ Gem::Specification.new do |spec|
6
6
  spec.email = ["hugo@blackbid.co"]
7
7
  spec.description = %q{Patriach is about adding behaviours on the fly to good old active record models.}
8
8
  spec.summary = %q{Manage relationships between instances of your models easily}
9
- spec.homepage = "https://github.com/blackbirdco"
9
+ spec.homepage = "https://github.com/giglemad/patriarch"
10
10
 
11
11
  spec.platform = Gem::Platform::RUBY
12
12
  spec.licenses = ['MIT']
13
+
14
+ spec.add_development_dependency "rspec"
15
+ spec.add_runtime_dependency "redis-objects"
13
16
 
14
- spec.files = `git ls-files`.split($\)
17
+ spec.files = `git ls-files`.split($\)
15
18
  spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
20
  spec.name = "patriarch"
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Patriarch::Behaviours do
5
+
6
+ # setup with empty fake klasses here
7
+
8
+ class ModelClass;
9
+ include Patriarch::Behaviours
10
+ end
11
+
12
+ class ModelKlass;
13
+ include Patriarch::Behaviours
14
+ end
15
+
16
+ ModelClass.add_behaviour :like, :on => [:modelKlass]
17
+ ModelKlass.add_behaviour :like, :by => [:modelClass]
18
+
19
+
20
+ context "when adding a behaviour" do
21
+ it "should implement basic tool methods" do
22
+ progressive_like = (Verbs::Conjugator.conjugate :like, :aspect => :progressive).split(/ /).last
23
+ ModelClass.new.should respond_to("#{"model_klass".pluralize}_i_like")
24
+ ModelClass.new.should respond_to("#{"model_klass".pluralize}_i_like_ids")
25
+ ModelKlass.new.should respond_to("#{"model_class".pluralize}_#{progressive_like}_me")
26
+ ModelKlass.new.should respond_to("#{"model_class".pluralize}_#{progressive_like}_me_ids")
27
+ end
28
+
29
+ it "should implement actions only on model being added behaviours with ':on' options" do
30
+ mc = ModelClass.new
31
+ mk = ModelKlass.new
32
+ mc.should respond_to(:like)
33
+ mc.should respond_to(:undo_like)
34
+ mk.should_not respond_to(:like)
35
+ mk.should_not respond_to(:undo_like)
36
+ end
37
+ end
38
+
39
+ context "when using an added behaviour" do
40
+ User.add_behaviour :smoke, :on => [:User]
41
+ User.add_behaviour :smoke, :by => [:User]
42
+
43
+ before(:all) do
44
+ @userA = create_user
45
+ @userB = create_user
46
+ @userA.smoke @userB
47
+ end
48
+
49
+ it "shall play the transaction nicely" do
50
+ @userA.users_i_smoke.should == [@userB]
51
+ @userB.users_smoking_me.should == [@userA]
52
+
53
+ @userA.users_i_smoke_ids.should == [@userB.id]
54
+ @userB.users_smoking_me_ids.should == [@userA.id]
55
+ end
56
+
57
+ it "shall play reverse transaction nicely" do
58
+ @userA.undo_smoke @userB
59
+ @userA.users_i_smoke.include?(@userB).should be_false
60
+ @userB.users_smoking_me.include?(@userA).should be_false
61
+
62
+
63
+
64
+ # @userA.users_i_smoke_ids.should be_empty
65
+ #@userB.users_smoking_me_ids.should be_empty
66
+ end
67
+
68
+ after(:all) do
69
+ @userA.undo_smoke @userB
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Patriarch::DAOServices::BipartiteRelationshipBuilderService do
4
+ before(:each) do
5
+ @instance = Patriarch::DAOServices::BipartiteRelationshipBuilderService.instance
6
+ @user = create_user
7
+ @item = create_item(@user)
8
+ @transac ||= Patriarch::TransactionServices::TransactionManagerService.instance.new_transaction(:like)
9
+ @transac.add_step(:like,@user,@item)
10
+ end
11
+
12
+ it "shall insert processable lambdas into queues when create is called" do
13
+ # Stubs the DAO retriever service so it gives DAO arrays back
14
+ dao_sample = {
15
+ :actor => Redis::SortedSet.new("user:1:redis_likes"),
16
+ :target => Redis::Set.new("item:1:redis_liker_users")
17
+ }
18
+ Patriarch::DAOServices::RetrieverService.instance.stub(:call).with(any_args()).and_return(dao_sample)
19
+
20
+ expect{
21
+ @instance.create(@transac)
22
+ }.to change{ @transac.transaction_queue.select{ |queue_element| queue_element.is_a? Proc}.size }.by(2)
23
+
24
+ expect{
25
+ @transac.transaction_queue.each{ |proc| proc.call }
26
+ }.to change{ $redis.keys.size}.by(2)
27
+ end
28
+
29
+ it "shall insert processable lambdas into queues when destroy is called" do
30
+ # Stubs the DAO retriever service so it gives DAO arrays back
31
+ dao_sample = {
32
+ :actor => Redis::SortedSet.new("user:1:redis_likes"),
33
+ :target => Redis::Set.new("item:1:redis_liker_users")
34
+ }
35
+ Patriarch::DAOServices::RetrieverService.instance.stub(:call).with(any_args()).and_return(dao_sample)
36
+
37
+
38
+ expect{
39
+ @instance.destroy(@transac)
40
+ }.to change{ @transac.transaction_queue.select{ |queue_element| queue_element.is_a? Proc}.size }.by(2)
41
+
42
+ expect{
43
+ @transac.transaction_queue.each{ |proc| proc.call }
44
+ }.to change{ $redis.info["total_commands_processed"].to_i}.by(3)
45
+ end
46
+
47
+ after(:each) do
48
+ $redis.del "user:#{@user.id}:redis_likes"
49
+ $redis.del "item:#{@item.id}:redis_liker_users"
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "reclip cleaner" do
4
+ before (:each) do
5
+ @recliper_user = create_user
6
+ @item = create_item(create_user)
7
+ @recliped_on_user = create_user
8
+ @recliper_user.reclip(@item,@recliped_on_user)
9
+ @reclip_id = @item.reclips_of_me_reclips_ids.first
10
+ end
11
+
12
+ it "shall clean reclips everywhere when given a single item" do
13
+ Patriarch::Behaviours::Reclip.clean_reclip_id(@reclip_id)
14
+
15
+ @recliped_on_user.reclipeds_on_me_reclips_ids.include?(@reclip_id).should be_false
16
+ @recliper_user.reclipeds_by_me_reclips_ids.include?(@reclip_id).should be_false
17
+ @item.reclips_of_me_reclips_ids.include?(@reclip_id).should be_false
18
+ end
19
+
20
+ it "shall clean all the reclip stuff (on him, by him) of a given user properly" do
21
+ pending('like the test above but in the adequate loop')
22
+ end
23
+
24
+ it "shall clean all the reclip stuff (on him, by him) of a given group properly" do
25
+ pending('like the test above but in the adequate loop')
26
+ end
27
+
28
+ it "shall clean all the reclip stuff of a given item properly" do
29
+ pending('like the test above but in the adequate loop')
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Patriarch::Services do
4
+ before (:each) do
5
+ @user = create_user
6
+ @item = create_item(@user)
7
+ end
8
+
9
+ context "LikeService" do
10
+ it "should register like informations properly" do
11
+ # Wrong way of doing tests, test should not know about what's inside ...
12
+ Patriarch::Services::Like::ManagerService.instance.resolve(@user,@item)
13
+ Redis::SortedSet.new("user:#{@user.id}:redis_likes").members.include?(@item.id.to_s).should be_true
14
+ Redis::SortedSet.new("item:#{@item.id}:redis_liker_users").members.include?(@user.id.to_s).should be_true
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe "reclip" do
4
+ before (:each) do
5
+ @recliper_user = create_user
6
+ @item = create_item(create_user)
7
+ @recliped_on_user = create_user
8
+ end
9
+
10
+ it "stores informations in redis properly" do
11
+ @recliper_user.reclip(@item,@recliped_on_user)
12
+
13
+ # Guy being recliped on remembers it
14
+ @recliped_on_user.reclipeds_on_me.should == [@item]
15
+ # Recliper remembers the reclip it did
16
+ @recliper_user.reclipeds_by_me.should == [@item]
17
+
18
+ # Item
19
+ @recliper_user.reclipeds_by_me.should have(1).reclip_hash
20
+ end
21
+
22
+ it "only updates information when clipped twice" do
23
+ @recliper_user.reclip(@item,@recliped_on_user)
24
+ old_time = @recliper_user.reclipeds_by_me(:with_scores => true).first[1]
25
+ @recliper_user.reclip(@item,@recliped_on_user)
26
+ new_time = @recliper_user.reclipeds_by_me(:with_scores => true).first[1]
27
+
28
+ new_time.should_not eq(old_time)
29
+
30
+ # Guy being recliped on remembers it
31
+ @recliped_on_user.reclipeds_on_me.should == [@item]
32
+ # Recliper remembers the reclip it did
33
+ @recliper_user.reclipeds_by_me.should == [@item]
34
+
35
+ # Item
36
+ @recliper_user.reclipeds_by_me.should have(1).reclip_hash
37
+ end
38
+
39
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Patriarch::DAOServices::RedisMapperService do
4
+ before(:each) do
5
+ @instance = Patriarch::DAOServices::RedisMapperService.instance
6
+ @context = {:relation_type => :like, :actor_type => :user, :target_type => :item,
7
+ :actor_id => 1, :target_id => 1}
8
+ @transaction_item = double()
9
+ @transaction_item.stub(:context).and_return(@context)
10
+ end
11
+
12
+ it "shall retrieve right DAO information" do
13
+ @instance.call(@transaction_item,:actor).should eq({:type => "sorted_set", :key => "redis_likes"})
14
+ @instance.call(@transaction_item,:target).should eq({:type => "sorted_set", :key => "redis_liker_users"})
15
+ end
16
+ end
17
+
18
+
19
+
20
+
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Patriarch::DAOServices::RetrieverService do
4
+ before(:each) do
5
+ @instance = Patriarch::DAOServices::RetrieverService.instance
6
+ @context = {:relation_type => :like, :actor_type => :user, :target_type => :item,
7
+ :actor_id => 1, :target_id => 1}
8
+ @transaction_item = double()
9
+ @transaction_item.stub(:actor_id).and_return(@context[:actor_id])
10
+ @transaction_item.stub(:actor_type).and_return(@context[:actor_type])
11
+ @transaction_item.stub(:target_type).and_return(@context[:target_type])
12
+ @transaction_item.stub(:target_id).and_return(@context[:target_id])
13
+ end
14
+
15
+ it "shall return nice Redis::Object instances" do
16
+ Patriarch::DAOServices::RedisMapperService.instance.should_receive(:call).twice.with(any_args()).and_return({:type => "sorted_set", :key =>"abc"})
17
+ dao_tab = @instance.call(@transaction_item)
18
+
19
+ dao_tab[:actor].key.should == "user:1:abc"
20
+ dao_tab[:actor].is_a?(Redis::SortedSet).should be_true
21
+
22
+ dao_tab[:target].key.should == "item:1:abc"
23
+ dao_tab[:target].is_a?(Redis::SortedSet).should be_true
24
+ end
25
+
26
+
27
+ it "shall reject types that are not suited" do
28
+ Patriarch::DAOServices::RedisMapperService.instance.should_receive(:call).with(any_args()).and_return({:type => "sorted", :key =>"abc"})
29
+ expect{
30
+ @instance.call(@transaction_item)
31
+ }.to raise_error(InvalidRedisTypeException)
32
+ end
33
+ end
34
+
35
+
36
+
37
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patriarch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,29 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-11-25 00:00:00.000000000 Z
13
- dependencies: []
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &23131200 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *23131200
25
+ - !ruby/object:Gem::Dependency
26
+ name: redis-objects
27
+ requirement: &23130660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *23130660
14
36
  description: Patriach is about adding behaviours on the fly to good old active record
15
37
  models.
16
38
  email:
@@ -24,10 +46,42 @@ files:
24
46
  - LICENSE
25
47
  - README.md
26
48
  - Rakefile
49
+ - lib/generators/patriarch/USAGE
50
+ - lib/generators/patriarch/patriarch_generator.rb
51
+ - lib/generators/patriarch/templates/after_manager_service.rb
52
+ - lib/generators/patriarch/templates/after_service.rb
53
+ - lib/generators/patriarch/templates/authorization_service.rb
54
+ - lib/generators/patriarch/templates/before_manager_service.rb
55
+ - lib/generators/patriarch/templates/before_service.rb
56
+ - lib/generators/patriarch/templates/empty_behaviour_module.rb
57
+ - lib/generators/patriarch/templates/manager_service.rb
58
+ - lib/generators/patriarch/templates/service.rb
59
+ - lib/generators/patriarch/templates/tools_methods.rb
60
+ - lib/generators/patriarch/templates/undo_service.rb
27
61
  - lib/patriarch.rb
62
+ - lib/patriarch/authorization_service.rb
63
+ - lib/patriarch/behaviours.rb
64
+ - lib/patriarch/dao_services/.DAOinstancier.rb.swp
65
+ - lib/patriarch/dao_services/bipartite_relationship_builder_service.rb
66
+ - lib/patriarch/dao_services/redis_mapper_service.rb
67
+ - lib/patriarch/dao_services/retriever_service.rb
68
+ - lib/patriarch/manager_service.rb
69
+ - lib/patriarch/service.rb
70
+ - lib/patriarch/tool_services/redis_cleaner.rb
71
+ - lib/patriarch/tool_services/redis_extractor_service.rb
72
+ - lib/patriarch/transaction.rb
73
+ - lib/patriarch/transaction_services/transaction_manager_service.rb
74
+ - lib/patriarch/transaction_step.rb
28
75
  - lib/patriarch/version.rb
29
76
  - patriarch.gemspec
30
- homepage: https://github.com/blackbirdco
77
+ - spec/lib/patriarch/add_behaviour_spec.rb
78
+ - spec/lib/patriarch/bipartite_relationship_builder_spec.rb
79
+ - spec/lib/patriarch/clean_reclip_spec.rb
80
+ - spec/lib/patriarch/like_service_spec.rb
81
+ - spec/lib/patriarch/reclip_spec.rb
82
+ - spec/lib/patriarch/redis_mapper_service_spec.rb
83
+ - spec/lib/patriarch/retriever_service_spec.rb
84
+ homepage: https://github.com/giglemad/patriarch
31
85
  licenses:
32
86
  - MIT
33
87
  post_install_message:
@@ -52,4 +106,11 @@ rubygems_version: 1.8.10
52
106
  signing_key:
53
107
  specification_version: 3
54
108
  summary: Manage relationships between instances of your models easily
55
- test_files: []
109
+ test_files:
110
+ - spec/lib/patriarch/add_behaviour_spec.rb
111
+ - spec/lib/patriarch/bipartite_relationship_builder_spec.rb
112
+ - spec/lib/patriarch/clean_reclip_spec.rb
113
+ - spec/lib/patriarch/like_service_spec.rb
114
+ - spec/lib/patriarch/reclip_spec.rb
115
+ - spec/lib/patriarch/redis_mapper_service_spec.rb
116
+ - spec/lib/patriarch/retriever_service_spec.rb