modis 1.4.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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