patriarch 0.0.1 → 0.0.2

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