modis 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,24 +11,24 @@ describe 'validations' do
11
11
 
12
12
  it 'responds to valid?' do
13
13
  model.name = nil
14
- model.valid?.should be_false
14
+ expect(model.valid?).to be false
15
15
  end
16
16
 
17
17
  it 'sets errors on the model' do
18
18
  model.name = nil
19
19
  model.valid?
20
- model.errors[:name].should eq ["can't be blank"]
20
+ expect(model.errors[:name]).to eq(["can't be blank"])
21
21
  end
22
22
 
23
23
  describe 'save' do
24
24
  it 'returns true if the model is valid' do
25
25
  model.name = "Ian"
26
- model.save.should be_true
26
+ expect(model.save).to be true
27
27
  end
28
28
 
29
29
  it 'returns false if the model is invalid' do
30
30
  model.name = nil
31
- model.save.should be_false
31
+ expect(model.save).to be false
32
32
  end
33
33
  end
34
34
 
@@ -36,7 +36,7 @@ describe 'validations' do
36
36
  it 'raises an error if the model is invalid' do
37
37
  model.name = nil
38
38
  expect do
39
- model.save!.should be_false
39
+ expect(model.save!).to be false
40
40
  end.to raise_error(Modis::RecordInvalid)
41
41
  end
42
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modis
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Leitch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-09 00:00:00.000000000 Z
11
+ date: 2014-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -75,27 +75,31 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".coveralls.yml"
77
77
  - ".gitignore"
78
+ - ".rubocop.yml"
78
79
  - ".ruby-gemset"
79
80
  - ".ruby-version"
80
81
  - ".travis.yml"
81
82
  - Gemfile
83
+ - Gemfile.lock
82
84
  - LICENSE.txt
83
85
  - README.md
84
86
  - Rakefile
85
87
  - lib/modis.rb
86
- - lib/modis/attributes.rb
88
+ - lib/modis/attribute.rb
87
89
  - lib/modis/configuration.rb
88
90
  - lib/modis/errors.rb
89
- - lib/modis/finders.rb
91
+ - lib/modis/finder.rb
92
+ - lib/modis/index.rb
90
93
  - lib/modis/model.rb
91
94
  - lib/modis/persistence.rb
92
95
  - lib/modis/transaction.rb
93
96
  - lib/modis/version.rb
94
- - lib/tasks/spec.rake
97
+ - lib/tasks/quality.rake
95
98
  - modis.gemspec
96
- - spec/attributes_spec.rb
99
+ - spec/attribute_spec.rb
97
100
  - spec/errors_spec.rb
98
- - spec/finders_spec.rb
101
+ - spec/finder_spec.rb
102
+ - spec/index_spec.rb
99
103
  - spec/persistence_spec.rb
100
104
  - spec/spec_helper.rb
101
105
  - spec/support/simplecov_helper.rb
@@ -126,9 +130,10 @@ signing_key:
126
130
  specification_version: 4
127
131
  summary: ActiveModel + Redis
128
132
  test_files:
129
- - spec/attributes_spec.rb
133
+ - spec/attribute_spec.rb
130
134
  - spec/errors_spec.rb
131
- - spec/finders_spec.rb
135
+ - spec/finder_spec.rb
136
+ - spec/index_spec.rb
132
137
  - spec/persistence_spec.rb
133
138
  - spec/spec_helper.rb
134
139
  - spec/support/simplecov_helper.rb
@@ -1,138 +0,0 @@
1
- module Modis
2
- module Attributes
3
- TYPES = [:string, :integer, :float, :timestamp, :boolean, :array, :hash]
4
-
5
- def self.included(base)
6
- base.extend ClassMethods
7
-
8
- base.instance_eval do
9
- bootstrap_attributes
10
- end
11
- end
12
-
13
- module ClassMethods
14
- def bootstrap_attributes
15
- class << self
16
- attr_accessor :attributes
17
- end
18
-
19
- self.attributes = {}
20
-
21
- attribute :id, :integer
22
- end
23
-
24
- def attribute(name, type = :string, options = {})
25
- name = name.to_s
26
- return if attributes.keys.include?(name)
27
- raise UnsupportedAttributeType.new(type) unless TYPES.include?(type)
28
-
29
- attributes[name] = options.update({ :type => type })
30
- define_attribute_methods [name]
31
- class_eval <<-EOS, __FILE__, __LINE__
32
- def #{name}
33
- attributes['#{name}']
34
- end
35
-
36
- def #{name}=(value)
37
- value = coerce_to_type('#{name}', value)
38
- #{name}_will_change! unless value == attributes['#{name}']
39
- attributes['#{name}'] = value
40
- end
41
- EOS
42
- end
43
- end
44
-
45
- def attributes
46
- @attributes ||= Hash[self.class.attributes.keys.zip]
47
- end
48
-
49
- def assign_attributes(hash)
50
- hash.each do |k, v|
51
- setter = "#{k}="
52
- send(setter, v) if respond_to?(setter)
53
- end
54
- end
55
-
56
- def write_attribute(key, value)
57
- attributes[key.to_s] = value
58
- end
59
-
60
- protected
61
-
62
- def set_sti_type
63
- if self.class.sti_child?
64
- assign_attributes(type: self.class.name)
65
- end
66
- end
67
-
68
- def reset_changes
69
- @changed_attributes.clear if @changed_attributes
70
- end
71
-
72
- def apply_defaults
73
- defaults = {}
74
- self.class.attributes.each do |attribute, options|
75
- defaults[attribute] = options[:default] if options[:default]
76
- end
77
- assign_attributes(defaults)
78
- end
79
-
80
- def coerce_to_string(attribute, value)
81
- attribute = attribute.to_s
82
- return if value.blank?
83
- type = self.class.attributes[attribute][:type]
84
- if type == :array || type == :hash
85
- MultiJson.encode(value) if value
86
- elsif type == :timestamp
87
- value.iso8601
88
- else
89
- value.to_s
90
- end
91
- end
92
-
93
- def coerce_to_type(attribute, value)
94
- # TODO: allow an attribute to be set to nil
95
- return if value.blank?
96
- attribute = attribute.to_s
97
- type = self.class.attributes[attribute][:type]
98
- strict = self.class.attributes[attribute][:strict] != false
99
-
100
- if type == :string
101
- value.to_s
102
- elsif type == :integer
103
- value.to_i
104
- elsif type == :float
105
- value.to_f
106
- elsif type == :timestamp
107
- return value if value.kind_of?(Time)
108
- Time.parse(value)
109
- elsif type == :boolean
110
- return true if [true, 'true'].include?(value)
111
- return false if [false, 'false'].include?(value)
112
- raise AttributeCoercionError.new("'#{value}' cannot be coerced to a :boolean.")
113
- elsif type == :array
114
- decode_json(value, Array, attribute, strict)
115
- elsif type == :hash
116
- decode_json(value, Hash, attribute, strict)
117
- else
118
- value
119
- end
120
- end
121
-
122
- def decode_json(value, type, attribute, strict)
123
- return value if value.kind_of?(type)
124
- begin
125
- value = MultiJson.decode(value) if value.kind_of?(String)
126
- rescue MultiJson::ParseError
127
- raise if strict
128
- else
129
- value = value.to_s unless strict
130
- end
131
- if strict
132
- return value if value.kind_of?(type)
133
- raise AttributeCoercionError.new("Expected #{attribute} to be an #{type}, got #{value.class} instead.")
134
- end
135
- value
136
- end
137
- end
138
- end
data/lib/modis/finders.rb DELETED
@@ -1,48 +0,0 @@
1
- module Modis
2
- module Finders
3
- def self.included(base)
4
- base.extend ClassMethods
5
- end
6
-
7
- module ClassMethods
8
- def find(id)
9
- record = attributes_for(id)
10
- model_class(record).new(record, new_record: false)
11
- end
12
-
13
- def all
14
- records = []
15
-
16
- Modis.with_connection do |redis|
17
- ids = redis.smembers(key_for(:all))
18
- records = redis.pipelined do
19
- ids.map { |id| redis.hgetall(key_for(id)) }
20
- end
21
- end
22
-
23
- records.map do |record|
24
- klass = model_class(record)
25
- klass.new(record, new_record: false)
26
- end
27
- end
28
-
29
- def attributes_for(id)
30
- if id.nil?
31
- raise RecordNotFound, "Couldn't find #{name} without an ID"
32
- end
33
- values = Modis.with_connection { |redis| redis.hgetall(key_for(id)) }
34
- unless values['id'].present?
35
- raise RecordNotFound, "Couldn't find #{name} with id=#{id}"
36
- end
37
- values
38
- end
39
-
40
- private
41
-
42
- def model_class(record)
43
- return self if record["type"].blank?
44
- return record["type"].constantize
45
- end
46
- end
47
- end
48
- end
data/lib/tasks/spec.rake DELETED
@@ -1,18 +0,0 @@
1
- begin
2
- require 'cane/rake_task'
3
-
4
- desc "Run cane to check quality metrics"
5
- Cane::RakeTask.new(:quality) do |cane|
6
- cane.add_threshold 'coverage/covered_percent', :>=, 99
7
- cane.no_style = false
8
- cane.style_measure = 1000
9
- cane.no_doc = true
10
- cane.abc_max = 25
11
- end
12
-
13
- namespace :spec do
14
- task :cane => ['spec', 'quality']
15
- end
16
- rescue LoadError
17
- warn "cane not available, quality task not provided."
18
- end
@@ -1,182 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module AttributesSpec
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 :hash_strict, :hash, strict: true
15
- attribute :hash_not_strict, :hash, strict: false
16
- end
17
- end
18
-
19
- describe Modis::Attributes do
20
- let(:model) { AttributesSpec::MockModel.new }
21
-
22
- it 'defines attributes' do
23
- model.name = 'bar'
24
- model.name.should == 'bar'
25
- end
26
-
27
- it 'applies an default value' do
28
- model.name.should eq 'Janet'
29
- model.age.should eq 60
30
- end
31
-
32
- it 'does not mark an attribute with a default as dirty' do
33
- model.name_changed?.should be_false
34
- end
35
-
36
- it 'raises an error for an unsupported attribute type' do
37
- expect do
38
- class AttributesSpec::MockModel
39
- attribute :unsupported, :symbol
40
- end
41
- end.to raise_error(Modis::UnsupportedAttributeType)
42
- end
43
-
44
- it 'assigns attributes' do
45
- model.assign_attributes(name: 'bar')
46
- model.name.should eq 'bar'
47
- end
48
-
49
- it 'does not attempt to assign attributes that are not defined on the model' do
50
- model.assign_attributes(missing_attr: 'derp')
51
- model.respond_to?(:missing_attr).should be_false
52
- end
53
-
54
- it 'allows an attribute to be nilled' do
55
- model.name = nil
56
- model.save!
57
- model.class.find(model.id).name.should be_nil
58
- end
59
-
60
- describe ':string type' do
61
- it 'is coerced' do
62
- model.name = 'Ian'
63
- model.save!
64
- found = AttributesSpec::MockModel.find(model.id)
65
- found.name.should eq 'Ian'
66
- end
67
- end
68
-
69
- describe ':integer type' do
70
- it 'is coerced' do
71
- model.age = 18
72
- model.save!
73
- found = AttributesSpec::MockModel.find(model.id)
74
- found.age.should eq 18
75
- end
76
- end
77
-
78
- describe ':float type' do
79
- it 'is coerced' do
80
- model.percentage = 18.6
81
- model.save!
82
- found = AttributesSpec::MockModel.find(model.id)
83
- found.percentage.should eq 18.6
84
- end
85
-
86
- it 'coerces a string representation to Float' do
87
- model.percentage = '18.6'
88
- model.save!
89
- found = AttributesSpec::MockModel.find(model.id)
90
- found.percentage.should eq 18.6
91
- end
92
- end
93
-
94
- describe ':timestamp type' do
95
- it 'is coerced' do
96
- now = Time.now
97
- model.created_at = now
98
- model.save!
99
- found = AttributesSpec::MockModel.find(model.id)
100
- found.created_at.should be_kind_of(Time)
101
- found.created_at.to_s.should eq now.to_s
102
- end
103
-
104
- it 'coerces a string representation to Time' do
105
- now = Time.now
106
- model.created_at = now.to_s
107
- model.save!
108
- found = AttributesSpec::MockModel.find(model.id)
109
- found.created_at.should be_kind_of(Time)
110
- found.created_at.to_s.should eq now.to_s
111
- end
112
- end
113
-
114
- describe ':boolean type' do
115
- it 'is coerced' do
116
- model.flag = 'true'
117
- model.save!
118
- found = AttributesSpec::MockModel.find(model.id)
119
- found.flag.should eq true
120
- end
121
-
122
- it 'raises an error if assigned a non-boolean value' do
123
- expect { model.flag = 'unf!' }.to raise_error(Modis::AttributeCoercionError)
124
- end
125
- end
126
-
127
- describe ':array type' do
128
- it 'is coerced' do
129
- model.array = [1, 2, 3]
130
- model.save!
131
- found = AttributesSpec::MockModel.find(model.id)
132
- found.array.should eq [1, 2, 3]
133
- end
134
-
135
- it 'raises an error when assigned another type' do
136
- expect { model.array = {foo: :bar} }.to raise_error(Modis::AttributeCoercionError)
137
- end
138
-
139
- it 'does not raise an error when assigned a JSON array string' do
140
- expect { model.array = "[1,2,3]" }.to_not raise_error
141
- end
142
-
143
- it 'does not raise an error when a JSON string does not deserialize to an Array' do
144
- expect { model.array = "{\"foo\":\"bar\"}" }.to raise_error(Modis::AttributeCoercionError)
145
- end
146
- end
147
-
148
- describe ':hash type' do
149
- it 'is coerced' do
150
- model.hash = {foo: :bar}
151
- model.save!
152
- found = AttributesSpec::MockModel.find(model.id)
153
- found.hash.should eq({'foo' => 'bar'})
154
- end
155
-
156
- describe 'strict: true' do
157
- it 'raises an error if the value cannot be decoded on assignment' do
158
- expect { model.hash_strict = "test" }.to raise_error(MultiJson::ParseError)
159
- end
160
-
161
- it 'does not raise an error if the value can be decoded on assignment' do
162
- expect { model.hash_strict = "{\"foo\":\"bar\"}" }.to_not raise_error
163
- end
164
- end
165
-
166
- describe 'strict: false' do
167
- it 'returns the value if it cannot be decoded' do
168
- model.write_attribute(:hash_not_strict, "test")
169
- model.save!
170
- found = AttributesSpec::MockModel.find(model.id)
171
- found.hash_not_strict.should eq "test"
172
- end
173
-
174
- it 'returns an integer as a string' do
175
- model.hash_not_strict = 1
176
- model.save!
177
- found = AttributesSpec::MockModel.find(model.id)
178
- found.hash_not_strict.should eq "1"
179
- end
180
- end
181
- end
182
- end