activerecord-typedstore 0.0.1 → 0.1.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.
- 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
|
[](http://travis-ci.org/byroot/activerecord-typedstore)
|
4
4
|
[](https://codeclimate.com/github/byroot/activerecord-typedstore)
|
5
|
+
[](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
|