modis 1.1.0 → 1.2.0

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.
@@ -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