promiscuous 0.100.5 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/promiscuous.rb +5 -1
  3. data/lib/promiscuous/config.rb +6 -5
  4. data/lib/promiscuous/dsl.rb +0 -4
  5. data/lib/promiscuous/loader.rb +0 -5
  6. data/lib/promiscuous/mongoid.rb +15 -5
  7. data/lib/promiscuous/publisher.rb +1 -1
  8. data/lib/promiscuous/publisher/model/active_record.rb +6 -1
  9. data/lib/promiscuous/publisher/model/base.rb +8 -11
  10. data/lib/promiscuous/publisher/model/mock.rb +2 -2
  11. data/lib/promiscuous/publisher/model/mongoid.rb +3 -4
  12. data/lib/promiscuous/publisher/operation/active_record.rb +13 -69
  13. data/lib/promiscuous/publisher/operation/atomic.rb +15 -158
  14. data/lib/promiscuous/publisher/operation/base.rb +13 -381
  15. data/lib/promiscuous/publisher/operation/ephemeral.rb +12 -8
  16. data/lib/promiscuous/publisher/operation/mongoid.rb +22 -92
  17. data/lib/promiscuous/publisher/operation/non_persistent.rb +0 -9
  18. data/lib/promiscuous/publisher/operation/proxy_for_query.rb +8 -6
  19. data/lib/promiscuous/publisher/operation/transaction.rb +4 -56
  20. data/lib/promiscuous/publisher/transport.rb +14 -0
  21. data/lib/promiscuous/publisher/transport/batch.rb +138 -0
  22. data/lib/promiscuous/publisher/transport/persistence.rb +14 -0
  23. data/lib/promiscuous/publisher/transport/persistence/active_record.rb +33 -0
  24. data/lib/promiscuous/publisher/transport/persistence/mongoid.rb +22 -0
  25. data/lib/promiscuous/publisher/transport/worker.rb +36 -0
  26. data/lib/promiscuous/publisher/worker.rb +3 -12
  27. data/lib/promiscuous/redis.rb +5 -0
  28. data/lib/promiscuous/subscriber/message.rb +1 -29
  29. data/lib/promiscuous/subscriber/model/base.rb +3 -2
  30. data/lib/promiscuous/subscriber/model/mongoid.rb +16 -1
  31. data/lib/promiscuous/subscriber/model/observer.rb +0 -1
  32. data/lib/promiscuous/subscriber/operation.rb +9 -3
  33. data/lib/promiscuous/subscriber/unit_of_work.rb +7 -7
  34. data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +1 -1
  35. data/lib/promiscuous/version.rb +1 -1
  36. metadata +39 -35
  37. data/lib/promiscuous/dependency.rb +0 -78
  38. data/lib/promiscuous/error/dependency.rb +0 -116
@@ -1,78 +0,0 @@
1
- require 'fnv'
2
-
3
- class Promiscuous::Dependency
4
- attr_accessor :internal_key, :version, :type
5
-
6
- def initialize(*args)
7
- options = args.extract_options!
8
- @type = options[:type]
9
- @owner = options[:owner]
10
- @dont_hash = options[:dont_hash]
11
-
12
- @internal_key = args.join('/')
13
-
14
- if @internal_key =~ /^[0-9]+$/
15
- @internal_key = @internal_key.to_i
16
- @hash = @internal_key
17
- else
18
- @hash = FNV.new.fnv1a_32(@internal_key)
19
-
20
- if Promiscuous::Config.hash_size.to_i > 0
21
- # We hash dependencies to have a O(1) memory footprint in Redis.
22
- # The hashing needs to be deterministic across instances in order to
23
- # function properly.
24
- @hash = @hash % Promiscuous::Config.hash_size.to_i
25
- @internal_key = @hash unless @dont_hash
26
- end
27
- end
28
-
29
- if @owner
30
- @internal_key = "#{@owner}:#{@internal_key}"
31
- end
32
- end
33
-
34
- def read?
35
- raise "Type not set" unless @type
36
- @type == :read
37
- end
38
-
39
- def write?
40
- raise "Type not set" unless @type
41
- @type == :write
42
- end
43
-
44
- def key(role)
45
- Promiscuous::Key.new(role).join(@internal_key)
46
- end
47
-
48
- def redis_node(distributed_redis=nil)
49
- distributed_redis ||= Promiscuous::Redis.master
50
- distributed_redis.nodes[@hash % distributed_redis.nodes.size]
51
- end
52
-
53
- def as_json(options={})
54
- @version ? [@internal_key, @version].join(':') : @internal_key
55
- end
56
-
57
- def self.parse(payload, options={})
58
- case payload
59
- when /^(.+):([0-9]+)$/ then new($1, options).tap { |d| d.version = $2.to_i }
60
- when /^(.+)$/ then new($1, options)
61
- end
62
- end
63
-
64
- def to_s
65
- as_json.to_s
66
- end
67
-
68
- # We need the eql? method to function properly (we use ==, uniq, ...) in operation
69
- # XXX The version is not taken in account.
70
- def eql?(other)
71
- self.internal_key == other.internal_key
72
- end
73
- alias == eql?
74
-
75
- def hash
76
- self.internal_key.hash
77
- end
78
- end
@@ -1,116 +0,0 @@
1
- class Promiscuous::Error::Dependency < Promiscuous::Error::Base
2
- attr_accessor :dependency_solutions, :operation
3
-
4
- def initialize(options={})
5
- self.operation = options[:operation]
6
- end
7
-
8
- # TODO Convert all that with Erb
9
-
10
- def message
11
- msg = nil
12
- case operation.operation
13
- when :read
14
- msg = "Promiscuous doesn't have any tracked dependencies to perform this multi read operation.\n" +
15
- "This is what you can do:\n\n" +
16
- " 1. Bypass Promiscuous\n\n" +
17
- " If you don't use the result of this operation in your following writes,\n" +
18
- " you can wrap your read query in a 'without_promiscuous { }' block.\n" +
19
- " This is the preferred solution when you are sure that the read doesn't\n" +
20
- " influence the value of a published attribute.\n\n" +
21
- " Rule of thumb: Predicates (methods ending with ?) are often suitable for this use case.\n\n"
22
- cnt = 2
23
- if operation.operation_ext != :count
24
- msg += " #{cnt}. Synchronize on individual instances\n\n" +
25
- " If the collection you are iterating through is small (<10), it becomes intersting\n" +
26
- " to track instances through their ids instead of the query selector. Example:\n\n" +
27
- " criteria.without_promiscuous.each do |doc|\n" +
28
- " next if doc.should_do_something?\n" +
29
- " doc.reload # tell promiscuous to track the instance\n" +
30
- " doc.do_something!\n" +
31
- " end\n\n"
32
- cnt += 1
33
- end
34
- if operation.selector_keys.present?
35
- msg += " #{cnt}. Track New Dependencies\n\n" +
36
- " Add #{operation.selector_keys.count == 1 ? "the following line" : "one of the following lines"} " +
37
- "in the #{operation.instance.class} model:\n\n" +
38
- " class #{operation.instance.class}\n" +
39
- operation.selector_keys.map { |field| " track_dependencies_of :#{field}" }.join("\n") + "\n" +
40
- " end\n\n" +
41
- (operation.selector_keys.count > 1 ?
42
- " The more specific field, the better. Promiscuous works better when working with small subsets\n" +
43
- " For example, tracking something like 'member_id' is a fairly safe choice.\n\n" : "") +
44
- " Note that dependency tracking slows down your writes. It can be seen as the analogous\n" +
45
- " of an index on a regular database.\n" +
46
- " You may find more information about the implications in the Promiscuous wiki (TODO:link).\n\n"
47
- end
48
- when :update
49
- msg = "Promiscuous cannot track dependencies of a multi update operation.\n" +
50
- "This is what you can do:\n\n" +
51
- " 1. Instead of doing a multi updates, update each instance separately\n\n" +
52
- " 2. Do not assign has_many associations directly, but use the << operator instead.\n\n"
53
- when :destroy
54
- msg = "Promiscuous cannot track dependencies of a multi delete operation.\n" +
55
- "This is what you can do:\n\n" +
56
- " 1. Instead of doing a multi delete, delete each instance separatly.\n\n" +
57
- " 2. Use destroy_all instead of destroy_all.\n\n" +
58
- " 3. Declare your has_many relationships with :dependent => :destroy instead of :delete.\n\n"
59
- end
60
-
61
- msg += "#{"-" * 100}\n\n"
62
-
63
- msg += "Promiscuous cannot allow the following "
64
- case operation.operation_ext || operation.operation
65
- when :count then msg += 'count'
66
- when :mapreduce then msg += 'mapreduce'
67
- when :read then msg += 'each loop'
68
- when :update then msg += 'multi update'
69
- when :destroy then msg += 'multi destroy'
70
- end
71
- msg += " #{self.class.explain_operation(self.operation)}"
72
- msg += "\n\nProTip: Try again with TRACE=2 in the shell or ENV['TRACE']='2' in the console.\n" unless ENV['TRACE']
73
- msg
74
- rescue Exception => e
75
- "#{e}\n#{e.backtrace.join("\n")}"
76
- end
77
-
78
- def self.explain_operation(operation, limit=100)
79
- instance = operation.instance
80
- selector = instance ? get_selector(instance.attributes, limit) : ""
81
- class_name = instance ? instance.class : "Unknown"
82
-
83
- if operation.operation == :create
84
- "#{instance.class}.create(#{selector})"
85
- else
86
- case operation.operation_ext || operation.operation
87
- when :count then verb = 'count'
88
- when :mapreduce then verb = 'mapreduce(...)'
89
- when :read then verb = operation.multi? ? 'each { ... }' : 'first'
90
- when :update then verb = operation.multi? ? 'update_all' : 'update'
91
- when :destroy then verb = operation.multi? ? 'delete_all' : 'delete'
92
- end
93
- msg = "#{class_name}#{selector.present? ? ".where(#{selector})" : ""}.#{verb}"
94
- if operation.operation == :update && operation.respond_to?(:change) && operation.change
95
- msg += "(#{get_selector(operation.change, limit)})"
96
- end
97
-
98
- if operation.operation == :commit
99
- msg = "Transaction commit"
100
- end
101
-
102
- msg
103
- end
104
- end
105
-
106
- def self.get_selector(attributes, limit=100)
107
- # TODO ActiveRecord?
108
- attributes = attributes['$set'] if attributes.count == 1 && attributes['$set']
109
- attributes.reject! { |k,v| v.nil? }
110
- selector = attributes.map { |k,v| ":#{k} => #{v}" }.join(", ")
111
- selector = "#{selector[0...(limit-3)]}..." if selector.size > limit
112
- selector
113
- end
114
-
115
- alias to_s message
116
- end