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,233 @@
1
+ module Modis
2
+ module Persistence
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.instance_eval do
6
+ class << self
7
+ attr_reader :sti_child
8
+ alias_method :sti_child?, :sti_child
9
+ end
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ # :nodoc:
15
+ def bootstrap_sti(parent, child)
16
+ child.instance_eval do
17
+ parent.instance_eval do
18
+ class << self
19
+ attr_accessor :sti_parent
20
+ end
21
+ attribute :type, :string unless attributes.key?('type')
22
+ end
23
+
24
+ @sti_child = true
25
+ @sti_parent = parent
26
+
27
+ bootstrap_attributes(parent)
28
+ bootstrap_indexes(parent)
29
+ end
30
+ end
31
+
32
+ def namespace
33
+ return sti_parent.namespace if sti_child?
34
+ @namespace ||= name.split('::').map(&:underscore).join(':')
35
+ end
36
+
37
+ def namespace=(value)
38
+ @namespace = value
39
+ @absolute_namespace = nil
40
+ end
41
+
42
+ def absolute_namespace
43
+ @absolute_namespace ||= [Modis.config.namespace, namespace].compact.join(':')
44
+ end
45
+
46
+ def key_for(id)
47
+ "#{absolute_namespace}:#{id}"
48
+ end
49
+
50
+ def create(attrs)
51
+ model = new(attrs)
52
+ model.save
53
+ model
54
+ end
55
+
56
+ def create!(attrs)
57
+ model = new(attrs)
58
+ model.save!
59
+ model
60
+ end
61
+
62
+ YAML_MARKER = '---'.freeze
63
+ def deserialize(record)
64
+ values = record.values
65
+ values = MessagePack.unpack(msgpack_array_header(values.size) + values.join)
66
+ keys = record.keys
67
+ values.each_with_index { |v, i| record[keys[i]] = v }
68
+ record
69
+ rescue MessagePack::MalformedFormatError
70
+ found_yaml = false
71
+
72
+ record.each do |k, v|
73
+ if v.start_with?(YAML_MARKER)
74
+ found_yaml = true
75
+ record[k] = YAML.load(v)
76
+ else
77
+ record[k] = MessagePack.unpack(v)
78
+ end
79
+ end
80
+
81
+ if found_yaml
82
+ id = record['id']
83
+ STDERR.puts "#{self}(id: #{id}) contains attributes serialized as YAML. As of Modis 1.4.0, YAML is no longer used as the serialization format. To improve performance loading this record, you can force the record to new serialization format (MessagePack) with: #{self}.find(#{id}).save!(yaml_sucks: true)"
84
+ end
85
+
86
+ record
87
+ end
88
+
89
+ private
90
+
91
+ def msgpack_array_header(n)
92
+ if n < 16
93
+ [0x90 | n].pack("C")
94
+ elsif n < 65536
95
+ [0xDC, n].pack("Cn")
96
+ else
97
+ [0xDD, n].pack("CN")
98
+ end.force_encoding(Encoding::UTF_8)
99
+ end
100
+ end
101
+
102
+ def persisted?
103
+ true
104
+ end
105
+
106
+ def key
107
+ new_record? ? nil : self.class.key_for(id)
108
+ end
109
+
110
+ def new_record?
111
+ defined?(@new_record) ? @new_record : true
112
+ end
113
+
114
+ def save(args = {})
115
+ create_or_update(args)
116
+ rescue Modis::RecordInvalid
117
+ false
118
+ end
119
+
120
+ def save!(args = {})
121
+ create_or_update(args) || (raise RecordNotSaved)
122
+ end
123
+
124
+ def destroy
125
+ self.class.transaction do |redis|
126
+ run_callbacks :destroy do
127
+ redis.pipelined do
128
+ remove_from_indexes(redis)
129
+ redis.srem(self.class.key_for(:all), id)
130
+ redis.del(key)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def reload
137
+ new_attributes = Modis.with_connection { |redis| self.class.attributes_for(redis, id) }
138
+ initialize(new_attributes)
139
+ self
140
+ end
141
+
142
+ def update_attribute(name, value)
143
+ assign_attributes(name => value)
144
+ save(validate: false)
145
+ end
146
+
147
+ def update_attributes(attrs)
148
+ assign_attributes(attrs)
149
+ save
150
+ end
151
+
152
+ def update_attributes!(attrs)
153
+ assign_attributes(attrs)
154
+ save!
155
+ end
156
+
157
+ private
158
+
159
+ def coerce_for_persistence(value)
160
+ value = [value.year, value.month, value.day, value.hour, value.min, value.sec, value.strftime("%:z")] if value.is_a?(Time)
161
+ MessagePack.pack(value)
162
+ end
163
+
164
+ def create_or_update(args = {})
165
+ validate(args)
166
+ future = persist(args[:yaml_sucks])
167
+
168
+ if future && (future == :unchanged || future.value == 'OK')
169
+ reset_changes
170
+ @new_record = false
171
+ true
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ def validate(args)
178
+ skip_validate = args.key?(:validate) && args[:validate] == false
179
+ return if skip_validate || valid?
180
+ raise Modis::RecordInvalid, errors.full_messages.join(', ')
181
+ end
182
+
183
+ def persist(persist_all)
184
+ future = nil
185
+ set_id if new_record?
186
+ callback = new_record? ? :create : :update
187
+
188
+ self.class.transaction do |redis|
189
+ run_callbacks :save do
190
+ run_callbacks callback do
191
+ redis.pipelined do
192
+ attrs = coerced_attributes(persist_all)
193
+ future = attrs.any? ? redis.hmset(self.class.key_for(id), attrs) : :unchanged
194
+
195
+ if new_record?
196
+ redis.sadd(self.class.key_for(:all), id)
197
+ add_to_indexes(redis)
198
+ else
199
+ update_indexes(redis)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ future
207
+ end
208
+
209
+ def coerced_attributes(persist_all) # rubocop:disable Metrics/AbcSize
210
+ attrs = []
211
+
212
+ if new_record? || persist_all
213
+ attributes.each do |k, v|
214
+ if (self.class.attributes[k][:default] || nil) != v
215
+ attrs << k << coerce_for_persistence(v)
216
+ end
217
+ end
218
+ else
219
+ changed_attributes.each do |k, _|
220
+ attrs << k << coerce_for_persistence(attributes[k])
221
+ end
222
+ end
223
+
224
+ attrs
225
+ end
226
+
227
+ def set_id
228
+ Modis.with_connection do |redis|
229
+ self.id = redis.incr("#{self.class.absolute_namespace}_id_seq")
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,13 @@
1
+ module Modis
2
+ module Transaction
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def transaction
9
+ Modis.with_connection { |redis| redis.multi { yield(redis) } }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Modis
2
+ VERSION = '1.4.1'
3
+ end
@@ -0,0 +1,41 @@
1
+ begin
2
+ if ENV['TRAVIS']
3
+ namespace :spec do
4
+ task cane: ['spec']
5
+ end
6
+ else
7
+ require 'cane/rake_task'
8
+
9
+ desc 'Run cane to check quality metrics'
10
+ Cane::RakeTask.new(:cane_quality) do |cane|
11
+ cane.add_threshold 'coverage/covered_percent', :>=, 99
12
+ cane.no_style = false
13
+ cane.style_measure = 1000
14
+ cane.no_doc = true
15
+ cane.abc_max = 25
16
+ end
17
+
18
+ namespace :spec do
19
+ task cane: %w(spec cane_quality)
20
+ end
21
+ end
22
+ rescue LoadError
23
+ warn "cane not available."
24
+
25
+ namespace :spec do
26
+ task cane: ['spec']
27
+ end
28
+ end
29
+
30
+ begin
31
+ require 'rubocop/rake_task'
32
+ t = RuboCop::RakeTask.new
33
+ t.options << '-D'
34
+ rescue LoadError
35
+ warn 'rubocop not available.'
36
+ task rubocop: ['spec']
37
+ end
38
+
39
+ namespace :spec do
40
+ task quality: %w(cane rubocop)
41
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'modis/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "modis"
8
+ gem.version = Modis::VERSION
9
+ gem.authors = ["Ian Leitch"]
10
+ gem.email = ["port001@gmail.com"]
11
+ gem.description = "ActiveModel + Redis"
12
+ gem.summary = "ActiveModel + Redis"
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'activemodel', '>= 3.0'
21
+ gem.add_runtime_dependency 'activesupport', '>= 3.0'
22
+ gem.add_runtime_dependency 'redis', '>= 3.0'
23
+ gem.add_runtime_dependency 'hiredis', '>= 0.5'
24
+ gem.add_runtime_dependency 'connection_pool', '>= 2'
25
+
26
+ if defined? JRUBY_VERSION
27
+ gem.platform = 'java'
28
+ gem.add_runtime_dependency 'msgpack-jruby'
29
+ else
30
+ gem.add_runtime_dependency 'msgpack', '>= 0.5'
31
+ end
32
+ end
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+
3
+ module AttributeSpec
4
+ class MockModel
5
+ include Modis::Model
6
+
7
+ attribute :name, :string, default: 'Janet'
8
+ attribute :age, :integer, default: 60
9
+ attribute :percentage, :float
10
+ attribute :created_at, :timestamp
11
+ attribute :flag, :boolean
12
+ attribute :array, :array
13
+ attribute :hash, :hash
14
+ attribute :string_or_hash, [:string, :hash]
15
+ end
16
+ end
17
+
18
+ describe Modis::Attribute do
19
+ let(:model) { AttributeSpec::MockModel.new }
20
+
21
+ it 'defines attributes' do
22
+ model.name = 'bar'
23
+ expect(model.name).to eq('bar')
24
+ end
25
+
26
+ it 'applies an default value' do
27
+ expect(model.name).to eq('Janet')
28
+ expect(model.age).to eq(60)
29
+ end
30
+
31
+ it 'does not mark an attribute with a default as dirty' do
32
+ expect(model.name_changed?).to be false
33
+ end
34
+
35
+ it 'raises an error for an unsupported attribute type' do
36
+ expect do
37
+ module AttributeSpec
38
+ class MockModel
39
+ attribute :unsupported, :symbol
40
+ end
41
+ end.to raise_error(Modis::UnsupportedAttributeType)
42
+ end
43
+ end
44
+
45
+ it 'assigns attributes' do
46
+ model.assign_attributes(name: 'bar')
47
+ expect(model.name).to eq 'bar'
48
+ end
49
+
50
+ it 'does not attempt to assign attributes that are not defined on the model' do
51
+ model.assign_attributes(missing_attr: 'derp')
52
+ expect(model.respond_to?(:missing_attrexpect)).to be false
53
+ end
54
+
55
+ it 'allows an attribute to be nilled' do
56
+ model.name = nil
57
+ model.save!
58
+ expect(model.class.find(model.id).name).to be_nil
59
+ end
60
+
61
+ it 'allows an attribute to be a blank string' do
62
+ model.name = ''
63
+ model.save!
64
+ expect(model.class.find(model.id).name).to eq('')
65
+ end
66
+
67
+ describe ':string type' do
68
+ it 'is coerced' do
69
+ model.name = 'Ian'
70
+ model.save!
71
+ found = AttributeSpec::MockModel.find(model.id)
72
+ expect(found.name).to eq('Ian')
73
+ end
74
+ end
75
+
76
+ describe ':integer type' do
77
+ it 'is coerced' do
78
+ model.age = 18
79
+ model.save!
80
+ found = AttributeSpec::MockModel.find(model.id)
81
+ expect(found.age).to eq(18)
82
+ end
83
+ end
84
+
85
+ describe ':float type' do
86
+ it 'is coerced' do
87
+ model.percentage = 18.6
88
+ model.save!
89
+ found = AttributeSpec::MockModel.find(model.id)
90
+ expect(found.percentage).to eq(18.6)
91
+ end
92
+ end
93
+
94
+ describe ':timestamp type' do
95
+ it 'is coerced' do
96
+ time = Time.new(2014, 12, 11, 17, 31, 50, '-02:00')
97
+ model.created_at = time
98
+ model.save!
99
+ found = AttributeSpec::MockModel.find(model.id)
100
+ expect(found.created_at).to be_kind_of(Time)
101
+ expect(found.created_at.to_s).to eq(time.to_s)
102
+ end
103
+ end
104
+
105
+ describe ':boolean type' do
106
+ describe true do
107
+ it 'is coerced' do
108
+ model.flag = true
109
+ model.save!
110
+ found = AttributeSpec::MockModel.find(model.id)
111
+ expect(found.flag).to be true
112
+ end
113
+ end
114
+
115
+ describe false do
116
+ it 'is coerced' do
117
+ model.flag = false
118
+ model.save!
119
+ found = AttributeSpec::MockModel.find(model.id)
120
+ expect(found.flag).to be false
121
+ end
122
+ end
123
+
124
+ it 'raises an error if assigned a non-boolean value' do
125
+ expect { model.flag = 'unf!' }.to raise_error(Modis::AttributeCoercionError, "Received value of type 'String', expected 'TrueClass', 'FalseClass' for attribute 'flag'.")
126
+ end
127
+ end
128
+
129
+ describe ':array type' do
130
+ it 'is coerced' do
131
+ model.array = [1, 2, 3]
132
+ model.save!
133
+ found = AttributeSpec::MockModel.find(model.id)
134
+ expect(found.array).to eq([1, 2, 3])
135
+ end
136
+
137
+ it 'raises an error when assigned another type' do
138
+ expect { model.array = { foo: :bar } }.to raise_error(Modis::AttributeCoercionError, "Received value of type 'Hash', expected 'Array' for attribute 'array'.")
139
+ end
140
+ end
141
+
142
+ describe ':hash type' do
143
+ it 'is coerced' do
144
+ model.hash = { foo: :bar }
145
+ model.save!
146
+ found = AttributeSpec::MockModel.find(model.id)
147
+ expect(found.hash).to eq('foo' => 'bar')
148
+ end
149
+
150
+ it 'raises an error when assigned another type' do
151
+ expect { model.hash = [] }.to raise_error(Modis::AttributeCoercionError, "Received value of type 'Array', expected 'Hash' for attribute 'hash'.")
152
+ end
153
+ end
154
+
155
+ describe 'variable type' do
156
+ it 'is coerced' do
157
+ model.string_or_hash = { foo: :bar }
158
+ model.save!
159
+ found = AttributeSpec::MockModel.find(model.id)
160
+ expect(found.string_or_hash).to eq('foo' => 'bar')
161
+
162
+ model.string_or_hash = 'test'
163
+ model.save!
164
+ found = AttributeSpec::MockModel.find(model.id)
165
+ expect(found.string_or_hash).to eq('test')
166
+ end
167
+
168
+ it 'raises an error when assigned another type' do
169
+ expect { model.string_or_hash = [] }.to raise_error(Modis::AttributeCoercionError, "Received value of type 'Array', expected 'String', 'Hash' for attribute 'string_or_hash'.")
170
+ end
171
+ end
172
+ end