store_attribute 0.5.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cd2643cf4b1909497a0de3f0d15ed6ca51ba599e
4
- data.tar.gz: 607171026ea9b9638e20a920b732bd7258db246a
2
+ SHA256:
3
+ metadata.gz: 941970a7a98bfa915d206d2fde758075a81bc610109426e0eaa74859d81bfde7
4
+ data.tar.gz: 3bb9f5fa94b6f9f321006172e9f8cb4a4e7c9ba7030364f8468c829989a3f5d7
5
5
  SHA512:
6
- metadata.gz: 79e570bd455ecf0f2f7c500725e4a243f4e0d5133c5bdfd45100e10963bbe16623c37396d4a87b7018c74472813efbcfd212cc9d7ebe83c697ca00ad053d338c
7
- data.tar.gz: 0c4458f32f0a658d276218894912e3c1c53833dd0cb4e506885bcff00a824beeadda620c89a4d6c09376edd1a262b9396ad317eb275c981d4d08194eb48968da
6
+ metadata.gz: ac9e82f2c78173e0054ea97f1c3d1318fd36b671b531b71d3f1e73be96a3247d7565b43cd27b21d3c2f01b6585143eeddb75a9cec99ef43cd77e0d43a3035130
7
+ data.tar.gz: edd8234b7dc2c704d8f3086e29641a9ff1fa866f6266341d71c6f566dfb8f72fed3d360b972b0c63c41a5c0a383af645655d75d8beabb731521ab97f4b38a8da
@@ -0,0 +1,29 @@
1
+ # Change log
2
+
3
+ ## master
4
+
5
+ ## 0.7.1
6
+
7
+ - Fixed bug with `store` called without accessors. ([@ioki-klaus][])
8
+
9
+ See [#10](https://github.com/palkan/store_attribute/pull/10).
10
+
11
+ ## 0.7.0 (2020-03-23)
12
+
13
+ - Added dirty tracking methods. ([@glaszig][])
14
+
15
+ [PR #8](https://github.com/palkan/store_attribute/pull/8).
16
+
17
+ ## 0.6.0 (2019-07-24)
18
+
19
+ - Added default values support. ([@dreikanter][], [@SumLare][])
20
+
21
+ See [PR #7](https://github.com/palkan/store_attribute/pull/7).
22
+
23
+ - Start keeping changelog. ([@palkan][])
24
+
25
+ [@palkan]: https://github.com/palkan
26
+ [@dreikanter]: https://github.com/dreikanter
27
+ [@SumLare]: https://github.com/SumLare
28
+ [@glaszig]: https://github.com/glaszig
29
+ [@ioki-klaus]: https://github.com/ioki-klaus
@@ -1,4 +1,4 @@
1
- Copyright 2016 palkan
1
+ Copyright 2016-2020 palkan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -4,21 +4,20 @@
4
4
 
5
5
  ActiveRecord extension which adds typecasting to store accessors.
6
6
 
7
- Compatible with Rails 4.2 and Rails 5.
7
+ Compatible with Rails 4.2 and Rails 5+.
8
8
 
9
- <a href="https://evilmartians.com/">
10
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
9
+ Extracted from not merged PR to Rails: [rails/rails#18942](https://github.com/rails/rails/pull/18942).
11
10
 
12
11
  ### Install
13
12
 
14
13
  In your Gemfile:
15
14
 
16
15
  ```ruby
17
- # for Rails 5
18
- gem "store_attribute", "~>0.5.0"
16
+ # for Rails 5+ (6 is supported)
17
+ gem "store_attribute", "~> 0.5.0"
19
18
 
20
19
  # for Rails 4.2
21
- gem "store_attribute", "~>0.4.0"
20
+ gem "store_attribute", "~> 0.4.0"
22
21
  ```
23
22
 
24
23
  ### Usage
@@ -26,14 +25,14 @@ gem "store_attribute", "~>0.4.0"
26
25
  You can use `store_attribute` method to add additional accessors with a type to an existing store on a model.
27
26
 
28
27
  ```ruby
29
- store_attribute(store_name, name, type, options = {})
28
+ store_attribute(store_name, name, type, options)
30
29
  ```
31
30
 
32
31
  Where:
33
32
  - `store_name` The name of the store.
34
33
  - `name` The name of the accessor to the store.
35
34
  - `type` A symbol such as `:string` or `:integer`, or a type object to be used for the accessor.
36
- - `options` A hash of cast type options such as `precision`, `limit`, `scale`.
35
+ - `options` (optional) A hash of cast type options such as `precision`, `limit`, `scale`, `default`.
37
36
 
38
37
  Type casting occurs every time you write data through accessor or update store itself
39
38
  and when object is loaded from database.
@@ -47,30 +46,36 @@ class MegaUser < User
47
46
  store_attribute :settings, :ratio, :integer, limit: 1
48
47
  store_attribute :settings, :login_at, :datetime
49
48
  store_attribute :settings, :active, :boolean
49
+ store_attribute :settings, :color, :string, default: "red"
50
+ store_attribute :settings, :data, :datetime, default: -> { Time.now }
50
51
  end
51
52
 
52
- u = MegaUser.new(active: false, login_at: '2015-01-01 00:01', ratio: "63.4608")
53
+ u = MegaUser.new(active: false, login_at: "2015-01-01 00:01", ratio: "63.4608")
53
54
 
54
55
  u.login_at.is_a?(DateTime) # => true
55
- u.login_at = DateTime.new(2015,1,1,11,0,0)
56
+ u.login_at = DateTime.new(2015, 1, 1, 11, 0, 0)
56
57
  u.ratio # => 63
57
58
  u.active # => false
59
+ # Default value is set
60
+ u.color # => red
61
+ # A dynamic default can also be provided
62
+ u.data # => Current time
58
63
  # And we also have a predicate method
59
64
  u.active? # => false
60
65
  u.reload
61
66
 
62
67
  # After loading record from db store contains casted data
63
- u.settings['login_at'] == DateTime.new(2015,1,1,11,0,0) # => true
68
+ u.settings["login_at"] == DateTime.new(2015, 1, 1, 11, 0, 0) # => true
64
69
 
65
70
  # If you update store explicitly then the value returned
66
71
  # by accessor isn't type casted
67
- u.settings['ration'] = "3.141592653"
72
+ u.settings["ratio"] = "3.141592653"
68
73
  u.ratio # => "3.141592653"
69
74
 
70
75
  # On the other hand, writing through accessor set correct data within store
71
76
  u.ratio = "3.141592653"
72
77
  u.ratio # => 3
73
- u.settings['ratio'] # => 3
78
+ u.settings["ratio"] # => 3
74
79
  ```
75
80
 
76
81
  You can also specify type using usual `store_accessor` method:
@@ -1,2 +1,4 @@
1
- require 'store_attribute/version'
2
- require 'store_attribute/active_record'
1
+ # frozen_string_literal: true
2
+
3
+ require "store_attribute/version"
4
+ require "store_attribute/active_record"
@@ -1 +1,3 @@
1
- require 'store_attribute/active_record/store'
1
+ # frozen_string_literal: true
2
+
3
+ require "store_attribute/active_record/store"
@@ -1,9 +1,12 @@
1
- require 'active_record/store'
2
- require 'store_attribute/active_record/type/typed_store'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/store"
4
+ require "store_attribute/active_record/type/typed_store"
3
5
 
4
6
  module ActiveRecord
5
7
  module Store
6
8
  module ClassMethods # :nodoc:
9
+ alias _orig_store store
7
10
  # Defines store on this model.
8
11
  #
9
12
  # +store_name+ The name of the store.
@@ -21,9 +24,18 @@ module ActiveRecord
21
24
  # store :settings, accessors: [:color, :homepage, login_at: :datetime], coder: JSON
22
25
  # end
23
26
  def store(store_name, options = {})
24
- serialize store_name, IndifferentCoder.new(options[:coder])
25
- store_accessor(store_name, *options[:accessors]) if options.key?(:accessors)
27
+ accessors = options.delete(:accessors)
28
+ typed_accessors =
29
+ if accessors && accessors.last.is_a?(Hash)
30
+ accessors.pop
31
+ else
32
+ {}
33
+ end
34
+
35
+ _orig_store(store_name, options)
36
+ store_accessor(store_name, *accessors, **typed_accessors) if accessors
26
37
  end
38
+
27
39
  # Adds additional accessors to an existing store on this model.
28
40
  #
29
41
  # +store_name+ The name of the store.
@@ -43,6 +55,9 @@ module ActiveRecord
43
55
 
44
56
  _define_accessors_methods(store_name, *keys)
45
57
 
58
+ _define_dirty_tracking_methods(store_name, keys)
59
+ _define_dirty_tracking_methods(store_name, typed_keys.keys)
60
+
46
61
  _prepare_local_stored_attributes(store_name, *keys)
47
62
 
48
63
  typed_keys.each do |key, type|
@@ -134,6 +149,50 @@ module ActiveRecord
134
149
  end
135
150
  end
136
151
  end
152
+
153
+ def _define_dirty_tracking_methods(store_attribute, keys)
154
+ _store_accessors_module.module_eval do
155
+ keys.flatten.each do |key|
156
+ key = key.to_s
157
+
158
+ define_method("#{key}_changed?") do
159
+ return false unless attribute_changed?(store_attribute)
160
+ prev_store, new_store = changes[store_attribute]
161
+ prev_store&.dig(key) != new_store&.dig(key)
162
+ end
163
+
164
+ define_method("#{key}_change") do
165
+ return unless attribute_changed?(store_attribute)
166
+ prev_store, new_store = changes[store_attribute]
167
+ [prev_store&.dig(key), new_store&.dig(key)]
168
+ end
169
+
170
+ define_method("#{key}_was") do
171
+ return unless attribute_changed?(store_attribute)
172
+ prev_store, _new_store = changes[store_attribute]
173
+ prev_store&.dig(key)
174
+ end
175
+
176
+ define_method("saved_change_to_#{key}?") do
177
+ return false unless saved_change_to_attribute?(store_attribute)
178
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
179
+ prev_store&.dig(key) != new_store&.dig(key)
180
+ end
181
+
182
+ define_method("saved_change_to_#{key}") do
183
+ return unless saved_change_to_attribute?(store_attribute)
184
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
185
+ [prev_store&.dig(key), new_store&.dig(key)]
186
+ end
187
+
188
+ define_method("#{key}_before_last_save") do
189
+ return unless saved_change_to_attribute?(store_attribute)
190
+ prev_store, _new_store = saved_change_to_attribute(store_attribute)
191
+ prev_store&.dig(key)
192
+ end
193
+ end
194
+ end
195
+ end
137
196
  end
138
197
  end
139
198
  end
@@ -1,4 +1,6 @@
1
- require 'active_record/type'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/type"
2
4
 
3
5
  module ActiveRecord
4
6
  module Type # :nodoc:
@@ -13,43 +15,57 @@ module ActiveRecord
13
15
 
14
16
  def initialize(subtype)
15
17
  @accessor_types = {}
18
+ @defaults = {}
16
19
  @store_accessor = subtype.accessor
17
20
  super(subtype)
18
21
  end
19
22
 
20
- def add_typed_key(key, type, **options)
21
- type = ActiveRecord::Type.lookup(type, options) if type.is_a?(Symbol)
22
- @accessor_types[key.to_s] = type
23
+ UNDEFINED = Object.new
24
+ private_constant :UNDEFINED
25
+
26
+ def add_typed_key(key, type, default: UNDEFINED, **options)
27
+ type = ActiveRecord::Type.lookup(type, **options) if type.is_a?(Symbol)
28
+ safe_key = key.to_s
29
+ @accessor_types[safe_key] = type
30
+ @defaults[safe_key] = default unless default == UNDEFINED
23
31
  end
24
32
 
25
33
  def deserialize(value)
26
34
  hash = super
27
- if hash
28
- accessor_types.each do |key, type|
29
- hash[key] = type.deserialize(hash[key]) if hash.key?(key)
35
+ return hash unless hash
36
+ accessor_types.each do |key, type|
37
+ if hash.key?(key)
38
+ hash[key] = type.deserialize(hash[key])
39
+ elsif defaults.key?(key)
40
+ hash[key] = get_default(key)
30
41
  end
31
42
  end
32
43
  hash
33
44
  end
34
45
 
35
46
  def serialize(value)
36
- if value.is_a?(Hash)
37
- typed_casted = {}
38
- accessor_types.each do |key, type|
39
- k = key_to_cast(value, key)
40
- typed_casted[k] = type.serialize(value[k]) unless k.nil?
47
+ return super(value) unless value.is_a?(Hash)
48
+ typed_casted = {}
49
+ accessor_types.each do |str_key, type|
50
+ key = key_to_cast(value, str_key)
51
+ next unless key
52
+ if value.key?(key)
53
+ typed_casted[key] = type.serialize(value[key])
54
+ elsif defaults.key?(str_key)
55
+ typed_casted[key] = type.serialize(get_default(str_key))
41
56
  end
42
- super(value.merge(typed_casted))
43
- else
44
- super(value)
45
57
  end
58
+ super(value.merge(typed_casted))
46
59
  end
47
60
 
48
61
  def cast(value)
49
62
  hash = super
50
- if hash
51
- accessor_types.each do |key, type|
52
- hash[key] = type.cast(hash[key]) if hash.key?(key)
63
+ return hash unless hash
64
+ accessor_types.each do |key, type|
65
+ if hash.key?(key)
66
+ hash[key] = type.cast(hash[key])
67
+ elsif defaults.key?(key)
68
+ hash[key] = get_default(key)
53
69
  end
54
70
  end
55
71
  hash
@@ -72,6 +88,7 @@ module ActiveRecord
72
88
  def key_to_cast(val, key)
73
89
  return key if val.key?(key)
74
90
  return key.to_sym if val.key?(key.to_sym)
91
+ return key if defaults.key?(key)
75
92
  end
76
93
 
77
94
  def typed?(key)
@@ -82,7 +99,12 @@ module ActiveRecord
82
99
  accessor_types.fetch(key.to_s)
83
100
  end
84
101
 
85
- attr_reader :accessor_types, :store_accessor
102
+ def get_default(key)
103
+ value = defaults.fetch(key)
104
+ value.is_a?(Proc) ? value.call : value
105
+ end
106
+
107
+ attr_reader :accessor_types, :defaults, :store_accessor
86
108
  end
87
109
  end
88
110
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StoreAttribute # :nodoc:
2
- VERSION = "0.5.1".freeze
4
+ VERSION = "0.7.1"
3
5
  end
metadata CHANGED
@@ -1,97 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_attribute
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-08 00:00:00.000000000 Z
11
+ date: 2020-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.0.0
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.0.0
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.18'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.18'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '10.1'
47
+ version: '13.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '10.1'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: simplecov
56
+ name: rubocop-md
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.3.8
61
+ version: '0.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.3.8
68
+ version: '0.3'
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry-byebug
70
+ name: standard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 0.2.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: 0.2.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: 3.5.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: 3.5.0
97
97
  description: ActiveRecord extension which adds typecasting to store accessors
@@ -101,34 +101,23 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - ".gitignore"
105
- - ".rspec"
106
- - ".rubocop.yml"
107
- - ".travis.yml"
108
- - Gemfile
109
- - MIT-LICENSE
104
+ - CHANGELOG.md
105
+ - LICENSE.txt
110
106
  - README.md
111
- - Rakefile
112
- - bench/bench.rb
113
- - bench/setup.rb
114
- - bin/console
115
- - bin/setup
116
- - gemfiles/rails5.gemfile
117
107
  - lib/store_attribute.rb
118
108
  - lib/store_attribute/active_record.rb
119
109
  - lib/store_attribute/active_record/store.rb
120
110
  - lib/store_attribute/active_record/type/typed_store.rb
121
111
  - lib/store_attribute/version.rb
122
- - spec/cases/store_attribute_spec.rb
123
- - spec/spec_helper.rb
124
- - spec/store_attribute/typed_store_spec.rb
125
- - spec/support/money_type.rb
126
- - spec/support/user.rb
127
- - store_attribute.gemspec
128
112
  homepage: http://github.com/palkan/store_attribute
129
113
  licenses:
130
114
  - MIT
131
- metadata: {}
115
+ metadata:
116
+ bug_tracker_uri: http://github.com/palkan/store_attribute/issues
117
+ changelog_uri: https://github.com/palkan/store_attribute/blob/master/CHANGELOG.md
118
+ documentation_uri: http://github.com/palkan/store_attribute
119
+ homepage_uri: http://github.com/palkan/store_attribute
120
+ source_code_uri: http://github.com/palkan/store_attribute
132
121
  post_install_message:
133
122
  rdoc_options: []
134
123
  require_paths:
@@ -137,15 +126,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
126
  requirements:
138
127
  - - ">="
139
128
  - !ruby/object:Gem::Version
140
- version: '0'
129
+ version: 2.4.0
141
130
  required_rubygems_version: !ruby/object:Gem::Requirement
142
131
  requirements:
143
132
  - - ">="
144
133
  - !ruby/object:Gem::Version
145
134
  version: '0'
146
135
  requirements: []
147
- rubyforge_project:
148
- rubygems_version: 2.6.4
136
+ rubygems_version: 3.0.6
149
137
  signing_key:
150
138
  specification_version: 4
151
139
  summary: ActiveRecord extension which adds typecasting to store accessors
data/.gitignore DELETED
@@ -1,37 +0,0 @@
1
- # Numerous always-ignore extensions
2
- *.diff
3
- *.err
4
- *.orig
5
- *.log
6
- *.rej
7
- *.swo
8
- *.swp
9
- *.vi
10
- *~
11
- *.sass-cache
12
- *.iml
13
- .idea/
14
-
15
- # Sublime
16
- *.sublime-project
17
- *.sublime-workspace
18
-
19
- # OS or Editor folders
20
- .DS_Store
21
- .cache
22
- .project
23
- .settings
24
- .tmproj
25
- Thumbs.db
26
- coverage/
27
-
28
- .bundle/
29
- *.log
30
- *.gem
31
- pkg/
32
- spec/dummy/log/*.log
33
- spec/dummy/tmp/
34
- spec/dummy/.sass-cache
35
- Gemfile.local
36
- Gemfile.lock
37
- tmp/
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --color
@@ -1,51 +0,0 @@
1
- AllCops:
2
- # Include gemspec and Rakefile
3
- Include:
4
- - 'lib/**/*.rb'
5
- - 'lib/**/*.rake'
6
- - 'spec/**/*.rb'
7
- Exclude:
8
- - 'bin/**/*'
9
- - 'spec/dummy/**/*'
10
- - 'tmp/**/*'
11
- - 'bench/**/*'
12
- DisplayCopNames: true
13
- StyleGuideCopsOnly: false
14
-
15
- Style/AccessorMethodName:
16
- Enabled: false
17
-
18
- Style/TrivialAccessors:
19
- Enabled: false
20
-
21
- Style/Documentation:
22
- Exclude:
23
- - 'spec/**/*.rb'
24
-
25
- Style/StringLiterals:
26
- Enabled: false
27
-
28
- Style/SpaceInsideStringInterpolation:
29
- EnforcedStyle: no_space
30
-
31
- Style/BlockDelimiters:
32
- Exclude:
33
- - 'spec/**/*.rb'
34
-
35
- Lint/AmbiguousRegexpLiteral:
36
- Enabled: false
37
-
38
- Metrics/MethodLength:
39
- Exclude:
40
- - 'spec/**/*.rb'
41
-
42
- Metrics/LineLength:
43
- Max: 100
44
- Exclude:
45
- - 'spec/**/*.rb'
46
-
47
- Rails/Date:
48
- Enabled: false
49
-
50
- Rails/TimeZone:
51
- Enabled: false
@@ -1,14 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
-
4
- addons:
5
- postgresql: "9.4"
6
-
7
- before_script:
8
- - createdb store_attribute_test
9
- - psql -U postgres -d store_attribute_test -c 'CREATE EXTENSION IF NOT EXISTS hstore;'
10
-
11
- matrix:
12
- include:
13
- - rvm: 2.3.1
14
- gemfile: gemfiles/rails5.gemfile
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- local_gemfile = 'Gemfile.local'
6
-
7
- if File.exist?(local_gemfile)
8
- eval(File.read(local_gemfile)) # rubocop:disable Lint/Eval
9
- else
10
- gem 'activerecord', '5.0.0'
11
- end
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- require 'rspec/core/rake_task'
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -1,38 +0,0 @@
1
- require 'benchmark/ips'
2
- require './setup'
3
-
4
- Benchmark.ips do |x|
5
- x.report('SA initialize') do
6
- User.new(public: '1', published_at: '2016-01-01', age: '23')
7
- end
8
-
9
- x.report('AR-T initialize') do
10
- Looser.new(public: '1', published_at: '2016-01-01', age: '23')
11
- end
12
- end
13
-
14
- Benchmark.ips do |x|
15
- x.report('SA accessors') do
16
- u = User.new
17
- u.public = '1'
18
- u.published_at = '2016-01-01'
19
- u.age = '23'
20
- end
21
-
22
- x.report('AR-T accessors') do
23
- u = Looser.new
24
- u.public = '1'
25
- u.published_at = '2016-01-01'
26
- u.age = '23'
27
- end
28
- end
29
-
30
- Benchmark.ips do |x|
31
- x.report('SA create') do
32
- User.create!(public: '1', published_at: '2016-01-01', age: '23')
33
- end
34
-
35
- x.report('AR-T create') do
36
- Looser.create(public: '1', published_at: '2016-01-01', age: '23')
37
- end
38
- end
@@ -1,67 +0,0 @@
1
- begin
2
- require 'bundler/inline'
3
- rescue LoadError => e
4
- $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
5
- raise e
6
- end
7
-
8
- gemfile(true) do
9
- source 'https://rubygems.org'
10
- gem 'activerecord', '~>4.2'
11
- gem 'pg'
12
- gem 'activerecord-typedstore', require: false
13
- gem 'pry-byebug'
14
- gem 'benchmark-ips'
15
- gem 'memory_profiler'
16
- end
17
-
18
- DB_NAME = ENV['DB_NAME'] || 'sa_bench'
19
-
20
- begin
21
- system("createdb #{DB_NAME}")
22
- rescue
23
- $stdout.puts "DB already exists"
24
- end
25
-
26
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
27
-
28
- require 'active_record'
29
- require 'logger'
30
- require 'store_attribute'
31
- require 'activerecord-typedstore'
32
-
33
- ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: DB_NAME)
34
-
35
- at_exit do
36
- ActiveRecord::Base.connection.disconnect!
37
- end
38
-
39
- module Bench
40
- module_function
41
- def setup_db
42
- ActiveRecord::Schema.define do
43
- create_table :users, force: true do |t|
44
- t.jsonb :data
45
- end
46
-
47
- create_table :loosers, force: true do |t|
48
- t.jsonb :data
49
- end
50
- end
51
- end
52
- end
53
-
54
- class User < ActiveRecord::Base
55
- store_accessor :data, public: :boolean, published_at: :datetime, age: :integer
56
- end
57
-
58
- class Looser < ActiveRecord::Base
59
- typed_store :data, coder: JSON do |s|
60
- s.boolean :public
61
- s.datetime :published_at
62
- s.integer :age
63
- end
64
- end
65
-
66
- # Run migration only if neccessary
67
- Bench.setup_db if ENV['FORCE'].present? || !ActiveRecord::Base.connection.tables.include?('users')
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "store_attribute"
5
-
6
- require "pry"
7
- Pry.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/bin/sh
2
-
3
- set -e
4
-
5
- gem install bundler --conservative
6
- bundle check || bundle install
7
-
8
- createdb store_attribute_test
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'rails', '~>5.0.0'
4
-
5
- gemspec path: '..'
@@ -1,171 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe StoreAttribute do
4
- before do
5
- @connection = ActiveRecord::Base.connection
6
-
7
- @connection.transaction do
8
- @connection.create_table('users') do |t|
9
- t.jsonb :jparams, default: {}, null: false
10
- t.text :custom
11
- t.hstore :hdata, default: {}, null: false
12
- end
13
- end
14
-
15
- User.reset_column_information
16
- end
17
-
18
- after do
19
- @connection.drop_table 'users', if_exists: true
20
- end
21
-
22
- let(:time) { DateTime.new(2015, 2, 14, 17, 0, 0) }
23
- let(:time_str) { '2015-02-14 17:00' }
24
- let(:time_str_utc) { '2015-02-14 17:00:00 UTC' }
25
-
26
- context "hstore" do
27
- it "typecasts on build" do
28
- user = User.new(visible: 't', login_at: time_str)
29
- expect(user.visible).to eq true
30
- expect(user).to be_visible
31
- expect(user.login_at).to eq time
32
- end
33
-
34
- it "typecasts on reload" do
35
- user = User.new(visible: 't', login_at: time_str)
36
- user.save!
37
- user = User.find(user.id)
38
-
39
- expect(user.visible).to eq true
40
- expect(user).to be_visible
41
- expect(user.login_at).to eq time
42
- end
43
-
44
- it "works with accessors" do
45
- user = User.new
46
- user.visible = false
47
- user.login_at = time_str
48
- user.save!
49
-
50
- user = User.find(user.id)
51
-
52
- expect(user.visible).to be false
53
- expect(user).not_to be_visible
54
- expect(user.login_at).to eq time
55
-
56
- ron = RawUser.find(user.id)
57
- expect(ron.hdata['visible']).to eq 'false'
58
- expect(ron.hdata['login_at']).to eq time_str_utc
59
- end
60
-
61
- it "handles options" do
62
- expect { User.create!(ratio: 1024) }.to raise_error(RangeError)
63
- end
64
-
65
- it "YAML roundtrip" do
66
- user = User.create!(visible: '0', login_at: time_str)
67
- dumped = YAML.load(YAML.dump(user))
68
-
69
- expect(dumped.visible).to be false
70
- expect(dumped.login_at).to eq time
71
- end
72
- end
73
-
74
- context "jsonb" do
75
- it "typecasts on build" do
76
- jamie = User.new(
77
- active: 'true',
78
- salary: 3.1999,
79
- birthday: '2000-01-01'
80
- )
81
- expect(jamie).to be_active
82
- expect(jamie.salary).to eq 3
83
- expect(jamie.birthday).to eq Date.new(2000, 1, 1)
84
- expect(jamie.jparams['birthday']).to eq Date.new(2000, 1, 1)
85
- expect(jamie.jparams['active']).to eq true
86
- end
87
-
88
- it "typecasts on reload" do
89
- jamie = User.create!(jparams: { 'active' => '1', 'birthday' => '01/01/2000', 'salary' => '3.14' })
90
- jamie = User.find(jamie.id)
91
-
92
- expect(jamie).to be_active
93
- expect(jamie.salary).to eq 3
94
- expect(jamie.birthday).to eq Date.new(2000, 1, 1)
95
- expect(jamie.jparams['birthday']).to eq Date.new(2000, 1, 1)
96
- expect(jamie.jparams['active']).to eq true
97
- end
98
-
99
- it "works with accessors" do
100
- john = User.new
101
- john.active = 1
102
-
103
- expect(john).to be_active
104
- expect(john.jparams['active']).to eq true
105
-
106
- john.jparams = { active: 'true', salary: '123.123', birthday: '01/01/2012' }
107
- expect(john).to be_active
108
- expect(john.birthday).to eq Date.new(2012, 1, 1)
109
- expect(john.salary).to eq 123
110
-
111
- john.save!
112
-
113
- ron = RawUser.find(john.id)
114
- expect(ron.jparams['active']).to eq true
115
- expect(ron.jparams['birthday']).to eq '2012-01-01'
116
- expect(ron.jparams['salary']).to eq 123
117
- end
118
-
119
- it "re-typecast old data" do
120
- jamie = User.create!
121
- User.update_all('jparams = \'{"active":"1", "salary":"12.02"}\'::jsonb')
122
-
123
- jamie = User.find(jamie.id)
124
- expect(jamie).to be_active
125
- expect(jamie.salary).to eq 12
126
-
127
- jamie.save!
128
-
129
- ron = RawUser.find(jamie.id)
130
- expect(ron.jparams['active']).to eq true
131
- expect(ron.jparams['salary']).to eq 12
132
- end
133
- end
134
-
135
- context "custom types" do
136
- it "typecasts on build" do
137
- user = User.new(price: "$1")
138
- expect(user.price).to eq 100
139
- end
140
-
141
- it "typecasts on reload" do
142
- jamie = User.create!(custom: { price: '$12' })
143
- expect(jamie.reload.price).to eq 1200
144
-
145
- jamie = User.find(jamie.id)
146
-
147
- expect(jamie.price).to eq 1200
148
- end
149
- end
150
-
151
- context "store subtype" do
152
- it "typecasts on build" do
153
- user = User.new(inner_json: { x: 1 })
154
- expect(user.inner_json).to eq('x' => 1)
155
- end
156
-
157
- it "typecasts on update" do
158
- user = User.new
159
- user.update!(inner_json: { x: 1 })
160
- expect(user.inner_json).to eq('x' => 1)
161
-
162
- expect(user.reload.inner_json).to eq('x' => 1)
163
- end
164
-
165
- it "typecasts on reload" do
166
- jamie = User.create!(inner_json: { x: 1 })
167
- jamie = User.find(jamie.id)
168
- expect(jamie.inner_json).to eq('x' => 1)
169
- end
170
- end
171
- end
@@ -1,41 +0,0 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
- $LOAD_PATH.unshift(File.dirname(__FILE__))
3
-
4
- if ENV['COVER']
5
- require 'simplecov'
6
- SimpleCov.root File.join(File.dirname(__FILE__), '..')
7
- SimpleCov.start
8
- end
9
-
10
- require 'rspec'
11
- require 'pry-byebug'
12
- require 'active_record'
13
- require 'pg'
14
- require 'store_attribute'
15
-
16
- ActiveRecord::Base.establish_connection(
17
- adapter: 'postgresql',
18
- database: 'store_attribute_test'
19
- )
20
- connection = ActiveRecord::Base.connection
21
-
22
- unless connection.extension_enabled?('hstore')
23
- connection.enable_extension 'hstore'
24
- connection.commit_db_transaction
25
- end
26
-
27
- connection.reconnect!
28
-
29
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
30
-
31
- RSpec.configure do |config|
32
- config.mock_with :rspec
33
-
34
- config.filter_run_when_matching :focus
35
-
36
- config.example_status_persistence_file_path = "tmp/rspec_examples.txt"
37
-
38
- if config.files_to_run.one?
39
- config.default_formatter = 'doc'
40
- end
41
- end
@@ -1,94 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActiveRecord::Type::TypedStore do
4
- let(:json_type) { ActiveRecord::Type::Serialized.new(ActiveRecord::Type::Text.new, ActiveRecord::Coders::JSON) }
5
- let(:yaml_type) do
6
- ActiveRecord::Type::Serialized.new(
7
- ActiveRecord::Type::Text.new,
8
- ActiveRecord::Store::IndifferentCoder.new(
9
- ActiveRecord::Coders::YAMLColumn.new(Hash)
10
- )
11
- )
12
- end
13
-
14
- context "with json store" do
15
- subject { described_class.new(json_type) }
16
-
17
- describe "#cast" do
18
- it "without key types", :aggregate_failures do
19
- expect(subject.cast([1, 2])).to eq [1, 2]
20
- expect(subject.cast('a' => 'b')).to eq('a' => 'b')
21
- end
22
-
23
- it "with type keys" do
24
- subject.add_typed_key('date', :date)
25
-
26
- date = ::Date.new(2016, 6, 22)
27
- expect(subject.cast(date: '2016-06-22')).to eq('date' => date)
28
- end
29
- end
30
-
31
- describe "#deserialize" do
32
- it "without key types", :aggregate_failures do
33
- expect(subject.deserialize('[1,2]')).to eq [1, 2]
34
- expect(subject.deserialize('{"a":"b"}')).to eq('a' => 'b')
35
- end
36
-
37
- it "with type keys" do
38
- subject.add_typed_key('date', :date)
39
-
40
- date = ::Date.new(2016, 6, 22)
41
- expect(subject.deserialize('{"date":"2016-06-22"}')).to eq('date' => date)
42
- end
43
- end
44
-
45
- describe "#serialize" do
46
- it "without key types", :aggregate_failures do
47
- expect(subject.serialize([1, 2])).to eq '[1,2]'
48
- expect(subject.serialize('a' => 'b')).to eq '{"a":"b"}'
49
- end
50
-
51
- it "with type keys" do
52
- subject.add_typed_key('date', :date)
53
-
54
- date = ::Date.new(2016, 6, 22)
55
- expect(subject.serialize(date: date)).to eq '{"date":"2016-06-22"}'
56
- end
57
-
58
- it "with type key with option" do
59
- subject.add_typed_key('val', :integer, limit: 1)
60
-
61
- expect { subject.serialize(val: 1024) }.to raise_error(RangeError)
62
- end
63
- end
64
-
65
- describe ".create_from_type" do
66
- it "creates with valid types", :aggregate_failures do
67
- type = described_class.create_from_type(json_type, 'date', :date)
68
- new_type = described_class.create_from_type(type, 'val', :integer)
69
-
70
- date = ::Date.new(2016, 6, 22)
71
-
72
- expect(type.cast(date: '2016-06-22', val: '1.2')).to eq('date' => date, 'val' => '1.2')
73
- expect(new_type.cast(date: '2016-06-22', val: '1.2')).to eq('date' => date, 'val' => 1)
74
- end
75
- end
76
- end
77
-
78
- context "with yaml coder" do
79
- let(:subject) { described_class.new(yaml_type) }
80
-
81
- it "works", :aggregate_failures do
82
- subject.add_typed_key('date', :date)
83
-
84
- date = ::Date.new(2016, 6, 22)
85
-
86
- expect(subject.cast(date: '2016-06-22')).to eq('date' => date)
87
- expect(subject.cast('date' => '2016-06-22')).to eq('date' => date)
88
- expect(subject.deserialize("---\n:date: 2016-06-22\n")).to eq('date' => date)
89
- expect(subject.deserialize("---\ndate: 2016-06-22\n")).to eq('date' => date)
90
- expect(subject.serialize(date: date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
91
- expect(subject.serialize('date' => date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
92
- end
93
- end
94
- end
@@ -1,12 +0,0 @@
1
- class MoneyType < ActiveRecord::Type::Integer
2
- def cast(value)
3
- if !value.is_a?(Numeric) && value.include?('$')
4
- price_in_dollars = value.delete('$').to_f
5
- super(price_in_dollars * 100)
6
- else
7
- super
8
- end
9
- end
10
- end
11
-
12
- ActiveRecord::Type.register(:money_type, MoneyType)
@@ -1,17 +0,0 @@
1
- class RawUser < ActiveRecord::Base
2
- self.table_name = 'users'
3
- end
4
-
5
- class User < ActiveRecord::Base
6
- store_accessor :jparams, :version, active: :boolean, salary: :integer
7
- store_attribute :jparams, :birthday, :date
8
-
9
- store_attribute :jparams, :inner_json, :json
10
-
11
- store :custom, accessors: [price: :money_type]
12
-
13
- store_accessor :hdata, visible: :boolean
14
-
15
- store_attribute :hdata, :ratio, :integer, limit: 1
16
- store_attribute :hdata, :login_at, :datetime
17
- end
@@ -1,27 +0,0 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
-
3
- # Maintain your gem's version:
4
- require "store_attribute/version"
5
-
6
- # Describe your gem and declare its dependencies:
7
- Gem::Specification.new do |s|
8
- s.name = "store_attribute"
9
- s.version = StoreAttribute::VERSION
10
- s.authors = ["palkan"]
11
- s.email = ["dementiev.vm@gmail.com"]
12
- s.homepage = "http://github.com/palkan/store_attribute"
13
- s.summary = "ActiveRecord extension which adds typecasting to store accessors"
14
- s.description = "ActiveRecord extension which adds typecasting to store accessors"
15
- s.license = "MIT"
16
-
17
- s.files = `git ls-files`.split($/)
18
- s.require_paths = ["lib"]
19
-
20
- s.add_runtime_dependency "activerecord", "~>5.0.0"
21
-
22
- s.add_development_dependency "pg", "~>0.18"
23
- s.add_development_dependency "rake", "~> 10.1"
24
- s.add_development_dependency "simplecov", ">= 0.3.8"
25
- s.add_development_dependency "pry-byebug"
26
- s.add_development_dependency "rspec", "~> 3.5.0"
27
- end