activerecord-typedstore 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/README.md +35 -5
- data/activerecord-typedstore.gemspec +3 -0
- data/gemfiles/Gemfile.ar-3.2 +3 -0
- data/gemfiles/Gemfile.ar-4.0 +4 -0
- data/gemfiles/Gemfile.ar-edge +3 -0
- data/lib/active_record/typed_store/ar_32_fallbacks.rb +74 -0
- data/lib/active_record/typed_store/ar_41_fallbacks.rb +45 -0
- data/lib/active_record/typed_store/column.rb +22 -4
- data/lib/active_record/typed_store/dsl.rb +1 -1
- data/lib/active_record/typed_store/extension.rb +18 -27
- data/lib/active_record/typed_store/version.rb +1 -1
- data/spec/active_record/typed_store_spec.rb +155 -11
- data/spec/spec_helper.rb +9 -0
- data/spec/support/models.rb +68 -24
- metadata +46 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6afeb6ff2bb89ff627f8705a5e7006db18f056f2
|
4
|
+
data.tar.gz: 502372b9082fb76972f43abc216d4cc8d95c4be2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0218e36856f3c37c08c0997f349ab7c610b528a1de19dd9bf24d312559922648a5ac9271fba86696254911688a51b62c0ee11500ed6b4af4d392834cda115126
|
7
|
+
data.tar.gz: cc586d21280e82597c3cdd11390737ca7286997fc87f6cdac785749fcda3754b10fdd9174eea633da2078dca0605c6b00fa2ec35e44f7e72da314eab6247632f
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://secure.travis-ci.org/byroot/activerecord-typedstore.png)](http://travis-ci.org/byroot/activerecord-typedstore)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/byroot/activerecord-typedstore.png)](https://codeclimate.com/github/byroot/activerecord-typedstore)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/byroot/activerecord-typedstore/badge.png)](https://coveralls.io/r/byroot/activerecord-typedstore)
|
5
6
|
|
6
7
|
[ActiveRecord::Store](http://api.rubyonrails.org/classes/ActiveRecord/Store.html) but with typed attributes.
|
7
8
|
|
@@ -22,7 +23,7 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
## Usage
|
24
25
|
|
25
|
-
It works exactly like [ActiveRecord::Store documentation](http://api.rubyonrails.org/classes/ActiveRecord/Store.html) but you
|
26
|
+
It works exactly like [ActiveRecord::Store documentation](http://api.rubyonrails.org/classes/ActiveRecord/Store.html) but you can declare the type of your attributes.
|
26
27
|
|
27
28
|
Attributes definition is similar to activerecord's migrations:
|
28
29
|
|
@@ -31,23 +32,52 @@ Attributes definition is similar to activerecord's migrations:
|
|
31
32
|
class Shop < ActiveRecord::Base
|
32
33
|
|
33
34
|
typed_store :settings do |s|
|
34
|
-
s.boolean :public, default: false
|
35
|
-
s.string :email
|
36
|
-
s.datetime :publish_at
|
35
|
+
s.boolean :public, default: false, null: false
|
36
|
+
s.string :email
|
37
|
+
s.datetime :publish_at
|
38
|
+
s.integer :age, null: false
|
39
|
+
|
40
|
+
# You can define array attributes like in rails 4 and postgres
|
41
|
+
s.string :tags, array: true, default: [], null: false
|
42
|
+
|
43
|
+
# In addition to prevent null values you can prevent blank values
|
44
|
+
s.string :title, blank: false, default: 'Title'
|
45
|
+
|
46
|
+
# If you don't want to enforce a datatype but still like to have default handling
|
47
|
+
s.any :source, blank: false, default: 'web'
|
37
48
|
end
|
38
49
|
|
50
|
+
# You can use any ActiveModel validator
|
51
|
+
validates :age, presence: true
|
52
|
+
|
39
53
|
end
|
40
54
|
|
55
|
+
# Values are accessible like normal model attributes
|
41
56
|
shop = Shop.new(email: 'george@cyclim.se')
|
42
57
|
shop.public? # => false
|
43
58
|
shop.email # => 'george@cyclim.se'
|
44
59
|
shop.published_at # => nil
|
60
|
+
|
61
|
+
# Values are type casted
|
62
|
+
shop.update_attributes(
|
63
|
+
age: '42',
|
64
|
+
published_at: '1984-06-08 13:57:12'
|
65
|
+
)
|
66
|
+
shop.age # => 42
|
67
|
+
shop.published_at.class #= DateTime
|
68
|
+
|
69
|
+
# You can still use it as a regular store
|
70
|
+
shop.settings[:unknown] = 'Hello World'
|
71
|
+
shop.save
|
72
|
+
shop.reload
|
73
|
+
shop.settings[:unknown] # => 'Hello World'
|
74
|
+
|
45
75
|
```
|
46
76
|
|
47
77
|
Type casting rules and attribute behavior are exactly the same as a for real database columns.
|
48
78
|
Actually the only difference is that you wont be able to query on these attributes (unless you use Postgres JSON or HStore types) and that you don't need to do a migration to add / remove an attribute.
|
49
79
|
|
50
|
-
If not please fill an issue.
|
80
|
+
If not, please fill an issue.
|
51
81
|
|
52
82
|
## Contributing
|
53
83
|
|
@@ -23,6 +23,9 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
24
|
spec.add_development_dependency 'rake'
|
25
25
|
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'coveralls'
|
26
27
|
spec.add_development_dependency 'sqlite3'
|
28
|
+
spec.add_development_dependency 'pg'
|
29
|
+
spec.add_development_dependency 'mysql2'
|
27
30
|
spec.add_development_dependency 'database_cleaner'
|
28
31
|
end
|
data/gemfiles/Gemfile.ar-3.2
CHANGED
data/gemfiles/Gemfile.ar-4.0
CHANGED
data/gemfiles/Gemfile.ar-edge
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module ActiveRecord::TypedStore
|
2
|
+
|
3
|
+
module AR32Fallbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def typed_store(store_attribute, options={}, &block)
|
9
|
+
dsl = super
|
10
|
+
_ar_32_fallback_accessors(store_attribute, dsl.columns)
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def _ar_32_fallback_accessors(store_attribute, columns)
|
16
|
+
_ar_32_fallback_initializer(store_attribute, columns)
|
17
|
+
columns.each do |column|
|
18
|
+
_ar_32_fallback_accessor(store_attribute, column)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _ar_32_fallback_initializer(store_attribute, columns)
|
23
|
+
define_method(store_attribute) do
|
24
|
+
initialized = "@_#{store_attribute}_initialized"
|
25
|
+
unless instance_variable_get(initialized)
|
26
|
+
store = read_attribute(store_attribute)
|
27
|
+
store = initialize_store(store, columns)
|
28
|
+
write_attribute(store_attribute, store)
|
29
|
+
instance_variable_set(initialized, true)
|
30
|
+
end
|
31
|
+
read_attribute(store_attribute)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _ar_32_fallback_accessor(store_attribute, column)
|
36
|
+
_ar_32_fallback_writer(store_attribute, column)
|
37
|
+
_ar_32_fallback_reader(store_attribute, column)
|
38
|
+
end
|
39
|
+
|
40
|
+
def _ar_32_fallback_writer(store_attribute, column)
|
41
|
+
define_method("#{column.name}_with_type_casting=") do |value|
|
42
|
+
self.send("#{column.name}_without_type_casting=", column.cast(value))
|
43
|
+
end
|
44
|
+
alias_method_chain "#{column.name}=", :type_casting
|
45
|
+
end
|
46
|
+
|
47
|
+
def _ar_32_fallback_reader(store_attribute, column)
|
48
|
+
define_method(column.name) do
|
49
|
+
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
|
50
|
+
store = send(store_attribute)
|
51
|
+
|
52
|
+
store.has_key?(column.name) ? store[column.name] : column.default
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def reload(*)
|
59
|
+
_ar_32_reload_stores!
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def _ar_32_reload_stores!
|
66
|
+
self.class.stored_typed_attributes.keys.each do |store_attribute|
|
67
|
+
instance_variable_set("@_#{store_attribute}_initialized", false)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
ActiveRecord::Base.send(:include, AR32Fallbacks)
|
74
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActiveRecord::TypedStore
|
2
|
+
|
3
|
+
module AR41Fallbacks
|
4
|
+
|
5
|
+
def reload(*)
|
6
|
+
_ar_41_reload_stores!
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def _ar_41_reload_stores!
|
13
|
+
self.class.stored_typed_attributes.keys.each do |store_attribute|
|
14
|
+
instance_variable_set("@_#{store_attribute}_initialized", false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module HashAccessorPatch
|
19
|
+
|
20
|
+
def self.extended(hash_accessor)
|
21
|
+
hash_accessor.singleton_class.alias_method_chain :prepare, :initialization
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def prepare_with_initialization(object, attribute)
|
27
|
+
prepare_without_initialization(object, attribute)
|
28
|
+
|
29
|
+
initialized = "@_#{attribute}_initialized"
|
30
|
+
unless object.instance_variable_get(initialized)
|
31
|
+
store = object.send(:initialize_store_attribute, attribute)
|
32
|
+
object.send("#{attribute}=", store)
|
33
|
+
object.instance_variable_set(initialized, true)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
ActiveRecord::Store::HashAccessor.extend(HashAccessorPatch)
|
41
|
+
ActiveRecord::Store::IndifferentHashAccessor.extend(HashAccessorPatch)
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveRecord::Base.send(:include, AR41Fallbacks)
|
45
|
+
end
|
@@ -1,24 +1,42 @@
|
|
1
1
|
module ActiveRecord::TypedStore
|
2
2
|
|
3
3
|
class Column < ::ActiveRecord::ConnectionAdapters::Column
|
4
|
+
attr_reader :array, :blank
|
4
5
|
|
5
6
|
def initialize(name, type, options={})
|
6
7
|
@name = name
|
7
8
|
@type = type
|
9
|
+
@array = options.fetch(:array, false)
|
8
10
|
@default = extract_default(options.fetch(:default, nil))
|
9
|
-
@null = options.fetch(:null,
|
11
|
+
@null = options.fetch(:null, true)
|
12
|
+
@blank = options.fetch(:blank, true)
|
10
13
|
end
|
11
14
|
|
12
|
-
def
|
15
|
+
def cast(value)
|
16
|
+
casted_value = type_cast(value)
|
17
|
+
if !blank
|
18
|
+
casted_value = default if casted_value.blank?
|
19
|
+
elsif !null
|
20
|
+
casted_value = default if casted_value.nil?
|
21
|
+
end
|
22
|
+
casted_value
|
23
|
+
end
|
24
|
+
|
25
|
+
def type_cast(value, map=true)
|
26
|
+
if array && (map || value.is_a?(Array))
|
27
|
+
return [] if map && !value.is_a?(Array)
|
28
|
+
return value.map{ |v| type_cast(v, false) }
|
29
|
+
end
|
30
|
+
|
13
31
|
if type == :string || type == :text
|
14
|
-
return value.to_s unless value.nil? && null
|
32
|
+
return value.to_s unless value.nil? && (null || array)
|
15
33
|
end
|
16
34
|
|
17
35
|
if IS_AR_3_2 && type == :datetime && value.is_a?(DateTime)
|
18
36
|
return super(value.iso8601)
|
19
37
|
end
|
20
38
|
|
21
|
-
super
|
39
|
+
super(value)
|
22
40
|
end
|
23
41
|
|
24
42
|
end
|
@@ -13,7 +13,7 @@ module ActiveRecord::TypedStore
|
|
13
13
|
@columns.map(&:name)
|
14
14
|
end
|
15
15
|
|
16
|
-
[:string, :integer, :float, :decimal, :datetime, :date, :boolean].each do |type|
|
16
|
+
[:string, :integer, :float, :decimal, :datetime, :date, :boolean, :any].each do |type|
|
17
17
|
define_method(type) do |name, options={}|
|
18
18
|
@columns << Column.new(name, type, options)
|
19
19
|
end
|
@@ -13,6 +13,8 @@ module ActiveRecord::TypedStore
|
|
13
13
|
included do
|
14
14
|
class_attribute :stored_typed_attributes, instance_accessor: false
|
15
15
|
self.stored_typed_attributes = {}
|
16
|
+
require 'active_record/typed_store/ar_32_fallbacks' if IS_AR_3_2
|
17
|
+
require 'active_record/typed_store/ar_41_fallbacks' if IS_AR_4_1
|
16
18
|
end
|
17
19
|
|
18
20
|
module ClassMethods
|
@@ -25,24 +27,7 @@ module ActiveRecord::TypedStore
|
|
25
27
|
stored_typed_attributes[store_attribute] ||= {}
|
26
28
|
stored_typed_attributes[store_attribute].merge!(dsl.columns.index_by(&:name))
|
27
29
|
|
28
|
-
|
29
|
-
after_initialize { initialize_store_attribute(store_attribute) }
|
30
|
-
end
|
31
|
-
|
32
|
-
if IS_AR_3_2
|
33
|
-
dsl.columns.each do |column|
|
34
|
-
define_method("#{column.name}_with_type_casting=") do |value|
|
35
|
-
self.send("#{column.name}_without_type_casting=", column.type_cast(value))
|
36
|
-
end
|
37
|
-
alias_method_chain "#{column.name}=", :type_casting
|
38
|
-
|
39
|
-
define_method(column.name) do
|
40
|
-
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
|
41
|
-
store = send(store_attribute)
|
42
|
-
store.has_key?(column.name) ? store[column.name] : column.default
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
30
|
+
dsl
|
46
31
|
end
|
47
32
|
|
48
33
|
end
|
@@ -53,26 +38,32 @@ module ActiveRecord::TypedStore
|
|
53
38
|
casted_value = value
|
54
39
|
if store_definition = self.class.stored_typed_attributes[store_attribute]
|
55
40
|
if column_definition = store_definition[key]
|
56
|
-
casted_value = column_definition.
|
41
|
+
casted_value = column_definition.cast(value)
|
57
42
|
end
|
58
43
|
end
|
44
|
+
|
59
45
|
super(store_attribute, key, casted_value)
|
60
46
|
end
|
61
47
|
|
62
48
|
private
|
63
49
|
|
64
50
|
def initialize_store_attribute(store_attribute)
|
65
|
-
|
51
|
+
store = IS_AR_4_0 ? super : send(store_attribute)
|
66
52
|
if columns = self.class.stored_typed_attributes[store_attribute]
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
53
|
+
store = initialize_store(store, columns.values)
|
54
|
+
end
|
55
|
+
store
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize_store(store, columns)
|
59
|
+
columns.each do |column|
|
60
|
+
if store.has_key?(column.name)
|
61
|
+
store[column.name] = column.cast(store[column.name])
|
62
|
+
else
|
63
|
+
store[column.name] = column.default if column.has_default?
|
73
64
|
end
|
74
65
|
end
|
75
|
-
|
66
|
+
store
|
76
67
|
end
|
77
68
|
|
78
69
|
end
|
@@ -4,10 +4,19 @@ ar_version = Gem::Version.new(ActiveRecord::VERSION::STRING)
|
|
4
4
|
ar_4_0 = Gem::Version.new('4.0')
|
5
5
|
ar_4_1 = Gem::Version.new('4.1.0.beta')
|
6
6
|
|
7
|
-
shared_examples '
|
7
|
+
shared_examples 'any model' do
|
8
8
|
|
9
9
|
let(:model) { described_class.new }
|
10
10
|
|
11
|
+
describe 'regular AR::Store' do
|
12
|
+
|
13
|
+
it 'save attributes as usual' do
|
14
|
+
model.update_attributes(title: 'The Big Lebowski')
|
15
|
+
expect(model.reload.title).to be == 'The Big Lebowski'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
11
20
|
describe 'build' do
|
12
21
|
|
13
22
|
it 'assign attributes received by #initialize' do
|
@@ -106,7 +115,7 @@ shared_examples 'a model' do
|
|
106
115
|
describe 'integer attributes' do
|
107
116
|
|
108
117
|
it 'has the defined default as initial value' do
|
109
|
-
expect(model.age).to
|
118
|
+
expect(model.age).to be == 12
|
110
119
|
end
|
111
120
|
|
112
121
|
it 'properly cast assigned value to integer' do
|
@@ -200,8 +209,8 @@ shared_examples 'a model' do
|
|
200
209
|
end
|
201
210
|
|
202
211
|
it 'nillify unparsable dates' do
|
203
|
-
model.
|
204
|
-
expect(model.
|
212
|
+
model.update_attributes(remind_on: 'foo')
|
213
|
+
expect(model.remind_on).to be_nil
|
205
214
|
end
|
206
215
|
|
207
216
|
it 'can store nil if the column is nullable' do
|
@@ -264,8 +273,8 @@ shared_examples 'a model' do
|
|
264
273
|
end
|
265
274
|
|
266
275
|
it 'nillify unparsable datetimes' do
|
267
|
-
model.
|
268
|
-
expect(model.
|
276
|
+
model.update_attributes(remind_at: 'foo')
|
277
|
+
expect(model.remind_at).to be_nil
|
269
278
|
end
|
270
279
|
|
271
280
|
it 'can store nil if the column is nullable' do
|
@@ -277,18 +286,153 @@ shared_examples 'a model' do
|
|
277
286
|
|
278
287
|
end
|
279
288
|
|
280
|
-
|
281
|
-
|
289
|
+
shared_examples 'a store' do
|
290
|
+
|
291
|
+
let(:model) { described_class.new }
|
292
|
+
|
293
|
+
describe 'attributes' do
|
294
|
+
|
295
|
+
it 'retrieve default if assigned nil and null not allowed' do
|
296
|
+
model.update_attributes(age: nil)
|
297
|
+
expect(model.age).to be == 12
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'retreive default if assigned a blank value and column cannot be blank' do
|
301
|
+
model.update_attributes(nickname: '')
|
302
|
+
expect(model.reload.nickname).to be == 'Please enter your nickname'
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
describe '`any` attributes' do
|
308
|
+
|
309
|
+
it 'accept any type' do
|
310
|
+
model.update_attributes(author: 'George')
|
311
|
+
expect(model.reload.author).to be == 'George'
|
312
|
+
|
313
|
+
model.update_attributes(author: 42)
|
314
|
+
expect(model.reload.author).to be == 42
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'still handle default' do
|
318
|
+
model.update_attributes(source: '')
|
319
|
+
expect(model.reload.source).to be == 'web'
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
shared_examples 'a db backed model' do
|
327
|
+
|
328
|
+
let(:model) { described_class.new }
|
329
|
+
|
330
|
+
it 'let the underlying db raise if assigned nil on non nullable column' do
|
331
|
+
expect {
|
332
|
+
model.update_attributes(age: nil)
|
333
|
+
}.to raise_error(ActiveRecord::StatementInvalid)
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
shared_examples 'a model supporting arrays' do |regular=false|
|
339
|
+
|
340
|
+
let(:model) { described_class.new }
|
341
|
+
|
342
|
+
it 'retrieve an array of values' do
|
343
|
+
model.update_attributes(grades: [1, 2, 3, 4])
|
344
|
+
expect(model.reload.grades).to be == [1, 2, 3, 4]
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'cast values inside the array (integer)' do
|
348
|
+
pending('ActiveRecord bug: https://github.com/rails/rails/pull/11245') if regular
|
349
|
+
model.update_attributes(grades: ['1', 2, 3.4])
|
350
|
+
expect(model.reload.grades).to be == [1, 2, 3]
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'cast values inside the array (string)' do
|
354
|
+
model.update_attributes(tags: [1, 2.3])
|
355
|
+
expect(model.reload.tags).to be == %w(1 2.3)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'accept nil inside array even if collumn is non nullable' do
|
359
|
+
model.update_attributes(tags: [1, nil])
|
360
|
+
expect(model.reload.tags).to be == ['1', nil]
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'convert non array value as empty array' do
|
364
|
+
model.update_attributes(grades: 'foo')
|
365
|
+
expect(model.reload.grades).to be == []
|
366
|
+
end
|
367
|
+
|
368
|
+
if !regular || ar_version == ar_4_1
|
369
|
+
it 'accept multidimensianl arrays' do
|
370
|
+
model.update_attributes(grades: [[1, 2], [3, 4]])
|
371
|
+
expect(model.reload.grades).to be == [[1, 2], [3, 4]]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
if regular
|
376
|
+
|
377
|
+
it 'raise on non rectangular multidimensianl arrays' do
|
378
|
+
expect{
|
379
|
+
model.update_attributes(grades: [[1, 2], [3, 4, 5]])
|
380
|
+
}.to raise_error(ActiveRecord::StatementInvalid)
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'raise on non nil assignation if column is non nullable' do
|
384
|
+
expect{
|
385
|
+
model.update_attributes(tags: nil)
|
386
|
+
}.to raise_error(ActiveRecord::StatementInvalid)
|
387
|
+
end
|
388
|
+
|
389
|
+
else
|
390
|
+
|
391
|
+
it 'accept non rectangular multidimensianl arrays' do
|
392
|
+
model.update_attributes(grades: [[1, 2], [3, 4, 5]])
|
393
|
+
expect(model.reload.grades).to be == [[1, 2], [3, 4, 5]]
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'retreive default if assigned null' do
|
397
|
+
model.update_attributes(tags: nil)
|
398
|
+
expect(model.reload.tags).to be == []
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
end
|
404
|
+
|
405
|
+
describe Sqlite3RegularARModel do
|
406
|
+
ActiveRecord::Base.establish_connection('test_sqlite3')
|
407
|
+
it_should_behave_like 'any model'
|
408
|
+
it_should_behave_like 'a db backed model'
|
409
|
+
end
|
410
|
+
|
411
|
+
describe MysqlRegularARModel do
|
412
|
+
it_should_behave_like 'any model'
|
413
|
+
it_should_behave_like 'a db backed model'
|
414
|
+
end
|
415
|
+
|
416
|
+
describe PostgresqlRegularARModel do
|
417
|
+
it_should_behave_like 'any model'
|
418
|
+
it_should_behave_like 'a db backed model'
|
419
|
+
it_should_behave_like 'a model supporting arrays', true if ar_version >= ar_4_0
|
282
420
|
end
|
283
421
|
|
284
422
|
describe YamlTypedStoreModel do
|
285
|
-
it_should_behave_like '
|
423
|
+
it_should_behave_like 'any model'
|
424
|
+
it_should_behave_like 'a store'
|
425
|
+
it_should_behave_like 'a model supporting arrays'
|
286
426
|
end
|
287
427
|
|
288
428
|
describe JsonTypedStoreModel do
|
289
|
-
it_should_behave_like '
|
429
|
+
it_should_behave_like 'any model'
|
430
|
+
it_should_behave_like 'a store'
|
431
|
+
it_should_behave_like 'a model supporting arrays'
|
290
432
|
end
|
291
433
|
|
292
434
|
describe MarshalTypedStoreModel do
|
293
|
-
it_should_behave_like '
|
435
|
+
it_should_behave_like 'any model'
|
436
|
+
it_should_behave_like 'a store'
|
437
|
+
it_should_behave_like 'a model supporting arrays'
|
294
438
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,15 @@ lib = File.expand_path('../lib', __FILE__)
|
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
3
|
|
4
4
|
require 'database_cleaner'
|
5
|
+
|
6
|
+
require 'simplecov'
|
7
|
+
require 'coveralls'
|
8
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
9
|
+
SimpleCov::Formatter::HTMLFormatter,
|
10
|
+
Coveralls::SimpleCov::Formatter
|
11
|
+
]
|
12
|
+
SimpleCov.start
|
13
|
+
|
5
14
|
require 'active_record/typed_store'
|
6
15
|
|
7
16
|
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f }
|
data/spec/support/models.rb
CHANGED
@@ -2,52 +2,92 @@ require 'active_record'
|
|
2
2
|
require 'json'
|
3
3
|
require 'yaml'
|
4
4
|
|
5
|
-
ActiveRecord::Base.configurations = {
|
6
|
-
|
5
|
+
ActiveRecord::Base.configurations = {
|
6
|
+
'test_sqlite3' => {adapter: 'sqlite3', database: "/tmp/typed_store.db"},
|
7
|
+
'test_postgresql' => {adapter: 'postgresql', database: 'typed_store_test', username: 'postgres'},
|
8
|
+
'test_mysql' => {adapter: 'mysql2', database: 'typed_store_test', username: 'travis'},
|
9
|
+
}
|
7
10
|
|
8
11
|
def define_columns(t)
|
9
12
|
t.integer :no_default
|
10
13
|
|
11
|
-
t.string :name, default: ''
|
12
|
-
t.string :email
|
14
|
+
t.string :name, default: '', null: false
|
15
|
+
t.string :email
|
13
16
|
|
14
|
-
t.boolean :public, default: false
|
15
|
-
t.boolean :enabled, default: true
|
17
|
+
t.boolean :public, default: false, null: false
|
18
|
+
t.boolean :enabled, default: true
|
16
19
|
|
17
|
-
t.integer :age, default:
|
18
|
-
t.integer :max_length
|
20
|
+
t.integer :age, default: 12, null: false
|
21
|
+
t.integer :max_length
|
19
22
|
|
20
|
-
t.float :rate, default: 0
|
21
|
-
t.float :price
|
23
|
+
t.float :rate, default: 0, null: false
|
24
|
+
t.float :price
|
22
25
|
|
23
|
-
t.date :published_on, default: '1984-06-08'
|
24
|
-
t.date :remind_on
|
26
|
+
t.date :published_on, default: '1984-06-08', null: false
|
27
|
+
t.date :remind_on
|
25
28
|
|
26
|
-
t.datetime :published_at, default: '1984-06-08 13:57:12'
|
27
|
-
t.datetime :remind_at
|
29
|
+
t.datetime :published_at, default: '1984-06-08 13:57:12', null: false
|
30
|
+
t.datetime :remind_at
|
28
31
|
|
29
|
-
t.decimal :total_price, default: 4.2
|
30
|
-
t.decimal :shipping_cost,
|
32
|
+
t.decimal :total_price, default: 4.2, null: false, precision: 16, scale: 2
|
33
|
+
t.decimal :shipping_cost, precision: 16, scale: 2
|
31
34
|
|
35
|
+
t.integer :grades, array: true
|
36
|
+
t.string :tags, array: true, null: false, default: []
|
37
|
+
|
38
|
+
t.string :nickname, blank: false, default: 'Please enter your nickname'
|
39
|
+
end
|
40
|
+
|
41
|
+
def define_store_columns(t)
|
42
|
+
define_columns(t)
|
43
|
+
t.any :author
|
44
|
+
t.any :source, blank: false, default: 'web'
|
32
45
|
end
|
33
46
|
|
34
47
|
class CreateAllTables < ActiveRecord::Migration
|
48
|
+
|
49
|
+
def self.recreate_table(name, *args, &block)
|
50
|
+
execute "drop table if exists #{name}"
|
51
|
+
create_table(name, *args, &block)
|
52
|
+
end
|
53
|
+
|
35
54
|
def self.up
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
55
|
+
ActiveRecord::Base.establish_connection('test_mysql')
|
56
|
+
recreate_table(:mysql_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
|
57
|
+
|
58
|
+
ActiveRecord::Base.establish_connection('test_postgresql')
|
59
|
+
recreate_table(:postgresql_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
|
60
|
+
|
61
|
+
ActiveRecord::Base.establish_connection('test_sqlite3')
|
62
|
+
recreate_table(:sqlite3_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
|
63
|
+
recreate_table(:yaml_typed_store_models) { |t| t.text :settings; t.text :untyped_settings }
|
64
|
+
recreate_table(:json_typed_store_models) { |t| t.text :settings; t.text :untyped_settings }
|
65
|
+
recreate_table(:marshal_typed_store_models) { |t| t.text :settings; t.text :untyped_settings }
|
40
66
|
end
|
41
67
|
end
|
42
68
|
ActiveRecord::Migration.verbose = false
|
43
69
|
CreateAllTables.up
|
44
70
|
|
45
|
-
class
|
71
|
+
class MysqlRegularARModel < ActiveRecord::Base
|
72
|
+
establish_connection 'test_mysql'
|
73
|
+
store :untyped_settings, accessors: [:title]
|
74
|
+
end
|
75
|
+
|
76
|
+
class PostgresqlRegularARModel < ActiveRecord::Base
|
77
|
+
establish_connection 'test_postgresql'
|
78
|
+
store :untyped_settings, accessors: [:title]
|
79
|
+
end
|
80
|
+
|
81
|
+
class Sqlite3RegularARModel < ActiveRecord::Base
|
82
|
+
establish_connection 'test_sqlite3'
|
83
|
+
store :untyped_settings, accessors: [:title]
|
46
84
|
end
|
47
85
|
|
48
86
|
class YamlTypedStoreModel < ActiveRecord::Base
|
87
|
+
establish_connection 'test_sqlite3'
|
88
|
+
store :untyped_settings, accessors: [:title]
|
49
89
|
typed_store :settings do |s|
|
50
|
-
|
90
|
+
define_store_columns(s)
|
51
91
|
end
|
52
92
|
end
|
53
93
|
|
@@ -69,13 +109,17 @@ class ColumnCoder
|
|
69
109
|
end
|
70
110
|
|
71
111
|
class JsonTypedStoreModel < ActiveRecord::Base
|
112
|
+
establish_connection 'test_sqlite3'
|
113
|
+
store :untyped_settings, accessors: [:title]
|
72
114
|
typed_store :settings, coder: ColumnCoder.new(JSON) do |s|
|
73
|
-
|
115
|
+
define_store_columns(s)
|
74
116
|
end
|
75
117
|
end
|
76
118
|
|
77
119
|
class MarshalTypedStoreModel < ActiveRecord::Base
|
120
|
+
establish_connection 'test_sqlite3'
|
121
|
+
store :untyped_settings, accessors: [:title]
|
78
122
|
typed_store :settings, coder: ColumnCoder.new(Marshal) do |s|
|
79
|
-
|
123
|
+
define_store_columns(s)
|
80
124
|
end
|
81
125
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-typedstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: sqlite3
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,34 @@ dependencies:
|
|
80
94
|
- - '>='
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pg
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mysql2
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
83
125
|
- !ruby/object:Gem::Dependency
|
84
126
|
name: database_cleaner
|
85
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,6 +155,8 @@ files:
|
|
113
155
|
- gemfiles/Gemfile.ar-4.0
|
114
156
|
- gemfiles/Gemfile.ar-edge
|
115
157
|
- lib/active_record/typed_store.rb
|
158
|
+
- lib/active_record/typed_store/ar_32_fallbacks.rb
|
159
|
+
- lib/active_record/typed_store/ar_41_fallbacks.rb
|
116
160
|
- lib/active_record/typed_store/column.rb
|
117
161
|
- lib/active_record/typed_store/dsl.rb
|
118
162
|
- lib/active_record/typed_store/extension.rb
|