modis 1.4.1-java

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.
@@ -0,0 +1,90 @@
1
+ # rubocop:disable all
2
+
3
+ require 'fileutils'
4
+ require 'redis/connection/hiredis'
5
+
6
+ class Redis
7
+ module Connection
8
+ class Fakedis < ::Redis::Connection::Hiredis
9
+ class << self
10
+ attr_accessor :reads, :read_indicies, :replaying, :recording
11
+ alias_method :replaying?, :replaying
12
+ alias_method :recording?, :recording
13
+ end
14
+
15
+ @reads = []
16
+ @read_indicies = []
17
+
18
+ def self.start_replay(name)
19
+ puts "Fakedis replaying."
20
+ self.replaying = true
21
+
22
+ @reads = Marshal.load(File.read(reads_path(name)))
23
+ @read_indicies = Marshal.load(File.read(read_indicies_path(name)))
24
+ end
25
+
26
+ def self.start_recording
27
+ puts "Fakedis recording."
28
+ self.recording = true
29
+ end
30
+
31
+ def self.stop_recording(name)
32
+ self.recording = false
33
+
34
+ puts "\nFakedis:"
35
+ puts " * #{reads.size} unique reads recorded"
36
+
37
+ FileUtils.mkdir_p("tmp/fakedis")
38
+
39
+ File.open(reads_path(name), 'w') { |fd| fd.write(Marshal.dump(reads)) }
40
+ File.open(read_indicies_path(name), 'w') { |fd| fd.write(Marshal.dump(read_indicies)) }
41
+ end
42
+
43
+ def self.reads_path(name)
44
+ "tmp/fakedis/#{name}_reads.dump"
45
+ end
46
+
47
+ def self.read_indicies_path(name)
48
+ "tmp/fakedis/#{name}_read_indicies.dump"
49
+ end
50
+
51
+ def initialize(*args)
52
+ super
53
+ @reads_idx = -1
54
+ @read_depth = 0
55
+ end
56
+
57
+ def read
58
+ if self.class.recording?
59
+ @read_depth += 1
60
+ v = super
61
+ @read_depth -= 1
62
+ return v if @read_depth > 0
63
+ i = self.class.reads.index(v)
64
+
65
+ if i
66
+ self.class.read_indicies << i
67
+ else
68
+ self.class.reads << v
69
+ self.class.read_indicies << self.class.reads.size - 1
70
+ end
71
+
72
+ v
73
+ elsif self.class.replaying?
74
+ @reads_idx += 1
75
+ self.class.reads[self.class.read_indicies[@reads_idx]]
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ def write(v)
82
+ if self.class.replaying?
83
+ # Do nothing.
84
+ else
85
+ super
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,41 @@
1
+ require 'redis'
2
+ require 'connection_pool'
3
+ require 'active_model'
4
+ require 'active_support/all'
5
+ require 'yaml'
6
+ require 'msgpack'
7
+
8
+ require 'modis/version'
9
+ require 'modis/configuration'
10
+ require 'modis/attribute'
11
+ require 'modis/errors'
12
+ require 'modis/persistence'
13
+ require 'modis/transaction'
14
+ require 'modis/finder'
15
+ require 'modis/index'
16
+ require 'modis/model'
17
+
18
+ module Modis
19
+ @mutex = Mutex.new
20
+
21
+ class << self
22
+ attr_accessor :connection_pool, :redis_options, :connection_pool_size,
23
+ :connection_pool_timeout
24
+ end
25
+
26
+ self.redis_options = { driver: :hiredis }
27
+ self.connection_pool_size = 5
28
+ self.connection_pool_timeout = 5
29
+
30
+ def self.connection_pool
31
+ return @connection_pool if @connection_pool
32
+ @mutex.synchronize do
33
+ options = { size: connection_pool_size, timeout: connection_pool_timeout }
34
+ @connection_pool = ConnectionPool.new(options) { Redis.new(redis_options) }
35
+ end
36
+ end
37
+
38
+ def self.with_connection
39
+ connection_pool.with { |connection| yield(connection) }
40
+ end
41
+ end
@@ -0,0 +1,103 @@
1
+ module Modis
2
+ module Attribute
3
+ TYPES = { string: [String],
4
+ integer: [Fixnum],
5
+ float: [Float],
6
+ timestamp: [Time],
7
+ hash: [Hash],
8
+ array: [Array],
9
+ boolean: [TrueClass, FalseClass] }.freeze
10
+
11
+ def self.included(base)
12
+ base.extend ClassMethods
13
+ base.instance_eval do
14
+ bootstrap_attributes
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def bootstrap_attributes(parent = nil)
20
+ attr_reader :attributes
21
+
22
+ class << self
23
+ attr_accessor :attributes, :attributes_with_defaults
24
+ end
25
+
26
+ self.attributes = parent ? parent.attributes.dup : {}
27
+ self.attributes_with_defaults = parent ? parent.attributes_with_defaults.dup : {}
28
+
29
+ attribute :id, :integer unless parent
30
+ end
31
+
32
+ def attribute(name, type, options = {})
33
+ name = name.to_s
34
+ raise AttributeError, "Attribute with name '#{name}' has already been specified." if attributes.key?(name)
35
+
36
+ type_classes = Array(type).map do |t|
37
+ raise UnsupportedAttributeType, t unless TYPES.key?(t)
38
+ TYPES[t]
39
+ end.flatten
40
+
41
+ attributes[name] = options.update(type: type)
42
+ attributes_with_defaults[name] = options[:default]
43
+ define_attribute_methods([name])
44
+
45
+ value_coercion = type == :timestamp ? 'value = Time.new(*value) if value && value.is_a?(Array) && value.count == 7' : nil
46
+ predicate = type_classes.map { |cls| "value.is_a?(#{cls.name})" }.join(' || ')
47
+
48
+ type_check = <<-RUBY
49
+ if value && !(#{predicate})
50
+ raise Modis::AttributeCoercionError, "Received value of type '\#{value.class}', expected '#{type_classes.join("', '")}' for attribute '#{name}'."
51
+ end
52
+ RUBY
53
+
54
+ class_eval <<-RUBY, __FILE__, __LINE__
55
+ def #{name}
56
+ attributes['#{name}']
57
+ end
58
+
59
+ def #{name}=(value)
60
+ #{value_coercion}
61
+
62
+ # ActiveSupport's Time#<=> does not perform well when comparing with NilClass.
63
+ if (value.nil? ^ attributes['#{name}'].nil?) || (value != attributes['#{name}'])
64
+ #{type_check}
65
+ #{name}_will_change!
66
+ attributes['#{name}'] = value
67
+ end
68
+ end
69
+ RUBY
70
+ end
71
+ end
72
+
73
+ def assign_attributes(hash)
74
+ hash.each do |k, v|
75
+ setter = "#{k}="
76
+ send(setter, v) if respond_to?(setter)
77
+ end
78
+ end
79
+
80
+ def write_attribute(key, value)
81
+ attributes[key.to_s] = value
82
+ end
83
+
84
+ def read_attribute(key)
85
+ attributes[key.to_s]
86
+ end
87
+
88
+ protected
89
+
90
+ def set_sti_type
91
+ return unless self.class.sti_child?
92
+ write_attribute(:type, self.class.name)
93
+ end
94
+
95
+ def reset_changes
96
+ @changed_attributes = nil
97
+ end
98
+
99
+ def apply_defaults
100
+ @attributes = Hash[self.class.attributes_with_defaults]
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,14 @@
1
+ module Modis
2
+ def self.configure
3
+ yield config
4
+ end
5
+
6
+ class Configuration < Struct.new(:namespace)
7
+ end
8
+
9
+ class << self
10
+ attr_reader :config
11
+ end
12
+
13
+ @config = Configuration.new
14
+ end
@@ -0,0 +1,16 @@
1
+ module Modis
2
+ class ModisError < StandardError; end
3
+ class RecordNotSaved < ModisError; end
4
+ class RecordNotFound < ModisError; end
5
+ class RecordInvalid < ModisError; end
6
+ class UnsupportedAttributeType < ModisError; end
7
+ class AttributeCoercionError < ModisError; end
8
+ class AttributeError < ModisError; end
9
+ class IndexError < ModisError; end
10
+
11
+ module Errors
12
+ def errors
13
+ @errors ||= ActiveModel::Errors.new(self)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,76 @@
1
+ module Modis
2
+ module Finder
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def find(*ids)
9
+ models = find_all(ids)
10
+ ids.count == 1 ? models.first : models
11
+ end
12
+
13
+ def all
14
+ records = Modis.with_connection do |redis|
15
+ ids = redis.smembers(key_for(:all))
16
+ redis.pipelined do
17
+ ids.map { |id| record_for(redis, id) }
18
+ end
19
+ end
20
+
21
+ records_to_models(records)
22
+ end
23
+
24
+ def attributes_for(redis, id)
25
+ raise RecordNotFound, "Couldn't find #{name} without an ID" if id.nil?
26
+
27
+ attributes = deserialize(record_for(redis, id))
28
+
29
+ unless attributes['id'].present?
30
+ raise RecordNotFound, "Couldn't find #{name} with id=#{id}"
31
+ end
32
+
33
+ attributes
34
+ end
35
+
36
+ def find_all(ids)
37
+ raise RecordNotFound, "Couldn't find #{name} without an ID" if ids.empty?
38
+
39
+ records = Modis.with_connection do |redis|
40
+ blk = proc { |id| record_for(redis, id) }
41
+ ids.count == 1 ? ids.map(&blk) : redis.pipelined { ids.map(&blk) }
42
+ end
43
+
44
+ models = records_to_models(records)
45
+
46
+ if models.count < ids.count
47
+ missing = ids - models.map(&:id)
48
+ raise RecordNotFound, "Couldn't find #{name} with id=#{missing.first}"
49
+ end
50
+
51
+ models
52
+ end
53
+
54
+ private
55
+
56
+ def records_to_models(records)
57
+ records.map do |record|
58
+ model_for(deserialize(record)) unless record.blank?
59
+ end.compact
60
+ end
61
+
62
+ def model_for(attributes)
63
+ model_class(attributes).new(attributes, new_record: false)
64
+ end
65
+
66
+ def record_for(redis, id)
67
+ redis.hgetall(key_for(id))
68
+ end
69
+
70
+ def model_class(record)
71
+ return self if record["type"].blank?
72
+ record["type"].constantize
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,84 @@
1
+ module Modis
2
+ module Index
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.instance_eval do
6
+ bootstrap_indexes
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def bootstrap_indexes(parent = nil)
12
+ class << self
13
+ attr_accessor :indexed_attributes
14
+ end
15
+
16
+ self.indexed_attributes = parent ? parent.indexed_attributes.dup : []
17
+ end
18
+
19
+ def index(attribute)
20
+ attribute = attribute.to_s
21
+ raise IndexError, "No such attribute '#{attribute}'" unless attributes.key?(attribute)
22
+ indexed_attributes << attribute
23
+ end
24
+
25
+ def where(query)
26
+ raise IndexError, 'Queries using multiple indexes is not currently supported.' if query.keys.size > 1
27
+ attribute, value = query.first
28
+ ids = index_for(attribute, value)
29
+ return [] if ids.empty?
30
+ find_all(ids)
31
+ end
32
+
33
+ def index_for(attribute, value)
34
+ Modis.with_connection do |redis|
35
+ key = index_key(attribute, value)
36
+ redis.smembers(key).map(&:to_i)
37
+ end
38
+ end
39
+
40
+ def index_key(attribute, value)
41
+ "#{absolute_namespace}:index:#{attribute}:#{value.inspect}"
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def indexed_attributes
48
+ self.class.indexed_attributes
49
+ end
50
+
51
+ def index_key(attribute, value)
52
+ self.class.index_key(attribute, value)
53
+ end
54
+
55
+ def add_to_indexes(redis)
56
+ return if indexed_attributes.empty?
57
+
58
+ indexed_attributes.each do |attribute|
59
+ key = index_key(attribute, read_attribute(attribute))
60
+ redis.sadd(key, id)
61
+ end
62
+ end
63
+
64
+ def remove_from_indexes(redis)
65
+ return if indexed_attributes.empty?
66
+
67
+ indexed_attributes.each do |attribute|
68
+ key = index_key(attribute, read_attribute(attribute))
69
+ redis.srem(key, id)
70
+ end
71
+ end
72
+
73
+ def update_indexes(redis)
74
+ return if indexed_attributes.empty?
75
+
76
+ (changes.keys & indexed_attributes).each do |attribute|
77
+ old_value, new_value = changes[attribute]
78
+ old_key = index_key(attribute, old_value)
79
+ new_key = index_key(attribute, new_value)
80
+ redis.smove(old_key, new_key, id)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,47 @@
1
+ module Modis
2
+ module Model
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ include ActiveModel::Dirty
6
+ include ActiveModel::Validations
7
+ include ActiveModel::Serialization
8
+
9
+ extend ActiveModel::Naming
10
+ extend ActiveModel::Callbacks
11
+
12
+ define_model_callbacks :save, :create, :update, :destroy
13
+
14
+ include Modis::Errors
15
+ include Modis::Transaction
16
+ include Modis::Persistence
17
+ include Modis::Finder
18
+ include Modis::Attribute
19
+ include Modis::Index
20
+
21
+ base.extend(ClassMethods)
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def inherited(child)
27
+ super
28
+ bootstrap_sti(self, child)
29
+ end
30
+ end
31
+
32
+ def initialize(record = nil, options = {})
33
+ apply_defaults
34
+ set_sti_type
35
+ assign_attributes(record) if record
36
+ reset_changes
37
+
38
+ return unless options.key?(:new_record)
39
+ instance_variable_set('@new_record', options[:new_record])
40
+ end
41
+
42
+ def ==(other)
43
+ super || other.instance_of?(self.class) && id.present? && other.id == id
44
+ end
45
+ alias_method :eql?, :==
46
+ end
47
+ end