store_attribute 0.6.0 → 0.7.0

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
2
  SHA256:
3
- metadata.gz: 44ef41ba6670ce7ce109d0a856948ba3ae113328037e9b52025b7a7229b6580d
4
- data.tar.gz: e25ade087cdad2fe69394462b227a0c1b580b4a0d977a8f505c6e28af916e143
3
+ metadata.gz: 721458d48d2bb0a7d065ce63e4e3c34829453f3f3060c8536cc4b5a40f378a12
4
+ data.tar.gz: 0fc7432f8f08b3f3e3d8e8c1c5a77d56267f2cdc6d31c802fbac87dc48dd15ae
5
5
  SHA512:
6
- metadata.gz: 29f7c336831838593456099308e7549e41a6d879585c1fb6f92dc2e48ba5150c4bba6856b2b9cb6e680c0b02882d8015f76d16bfcabab2ab73476d7e7dc4bc47
7
- data.tar.gz: 0ca99dc3856dfef871cf57729bef285118e2a775b9dbb4b64038d39c2f6480aa9d6c162e97d54ffa7ea5d0d100cd3fccc016658268e76dcba6bed3d2ef7e0fa5
6
+ metadata.gz: f598a6db05ed959744fb1959d7b3675b99e15459668ec4ca957276ada7fc5915cbdcdaedd88a46aa57805d11c99ef710adcd56d45d27e1aa9d7b5eb31fff4974
7
+ data.tar.gz: 5bca850c0073164d07f607482e4711e0341a06521f3129c92ef1faaf47e0d76b3fa9701d6a37648a3ba98be3b57f9d6afe2986a18a3aab916c0e1f4432d0896f
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## master
4
4
 
5
+ - Added dirty tracking methods. ([@glaszig][])
6
+
7
+ [PR #8](https://github.com/palkan/store_attribute/pull/8).
8
+
5
9
  ## 0.6.0 (2019-07-24)
6
10
 
7
11
  - Added default values support. ([@dreikanter][], [@SumLare][])
@@ -13,3 +17,4 @@
13
17
  [@palkan]: https://github.com/palkan
14
18
  [@dreikanter]: https://github.com/dreikanter
15
19
  [@SumLare]: https://github.com/SumLare
20
+ [@glaszig]: https://github.com/glaszig
@@ -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
@@ -25,8 +25,15 @@ module ActiveRecord
25
25
  # end
26
26
  def store(store_name, options = {})
27
27
  accessors = options.delete(:accessors)
28
+ typed_accessors =
29
+ if accessors.last.is_a?(Hash)
30
+ accessors.pop
31
+ else
32
+ {}
33
+ end
34
+
28
35
  _orig_store(store_name, options)
29
- store_accessor(store_name, *accessors) if accessors
36
+ store_accessor(store_name, *accessors, **typed_accessors) if accessors
30
37
  end
31
38
 
32
39
  # Adds additional accessors to an existing store on this model.
@@ -48,6 +55,9 @@ module ActiveRecord
48
55
 
49
56
  _define_accessors_methods(store_name, *keys)
50
57
 
58
+ _define_dirty_tracking_methods(store_name, keys)
59
+ _define_dirty_tracking_methods(store_name, typed_keys.keys)
60
+
51
61
  _prepare_local_stored_attributes(store_name, *keys)
52
62
 
53
63
  typed_keys.each do |key, type|
@@ -139,6 +149,50 @@ module ActiveRecord
139
149
  end
140
150
  end
141
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
142
196
  end
143
197
  end
144
198
  end
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  private_constant :UNDEFINED
25
25
 
26
26
  def add_typed_key(key, type, default: UNDEFINED, **options)
27
- type = ActiveRecord::Type.lookup(type, options) if type.is_a?(Symbol)
27
+ type = ActiveRecord::Type.lookup(type, **options) if type.is_a?(Symbol)
28
28
  safe_key = key.to_s
29
29
  @accessor_types[safe_key] = type
30
30
  @defaults[safe_key] = default unless default == UNDEFINED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoreAttribute # :nodoc:
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_attribute
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-24 00:00:00.000000000 Z
11
+ date: 2020-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -44,84 +44,42 @@ dependencies:
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'
55
- - !ruby/object:Gem::Dependency
56
- name: simplecov
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 0.3.8
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 0.3.8
69
- - !ruby/object:Gem::Dependency
70
- name: pry-byebug
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'
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 0.65.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 0.65.0
54
+ version: '13.0'
97
55
  - !ruby/object:Gem::Dependency
98
56
  name: rubocop-md
99
57
  requirement: !ruby/object:Gem::Requirement
100
58
  requirements:
101
59
  - - "~>"
102
60
  - !ruby/object:Gem::Version
103
- version: '0.2'
61
+ version: '0.3'
104
62
  type: :development
105
63
  prerelease: false
106
64
  version_requirements: !ruby/object:Gem::Requirement
107
65
  requirements:
108
66
  - - "~>"
109
67
  - !ruby/object:Gem::Version
110
- version: '0.2'
68
+ version: '0.3'
111
69
  - !ruby/object:Gem::Dependency
112
70
  name: standard
113
71
  requirement: !ruby/object:Gem::Requirement
114
72
  requirements:
115
73
  - - "~>"
116
74
  - !ruby/object:Gem::Version
117
- version: 0.0.36
75
+ version: 0.2.0
118
76
  type: :development
119
77
  prerelease: false
120
78
  version_requirements: !ruby/object:Gem::Requirement
121
79
  requirements:
122
80
  - - "~>"
123
81
  - !ruby/object:Gem::Version
124
- version: 0.0.36
82
+ version: 0.2.0
125
83
  - !ruby/object:Gem::Dependency
126
84
  name: rspec
127
85
  requirement: !ruby/object:Gem::Requirement
@@ -143,33 +101,14 @@ executables: []
143
101
  extensions: []
144
102
  extra_rdoc_files: []
145
103
  files:
146
- - ".gitignore"
147
- - ".rspec"
148
- - ".rubocop.yml"
149
- - ".travis.yml"
150
104
  - CHANGELOG.md
151
- - Gemfile
152
- - MIT-LICENSE
105
+ - LICENSE.txt
153
106
  - README.md
154
- - Rakefile
155
- - bench/bench.rb
156
- - bench/setup.rb
157
- - bin/console
158
- - bin/setup
159
- - gemfiles/rails5.gemfile
160
- - gemfiles/rails6.gemfile
161
- - gemfiles/railsmaster.gemfile
162
107
  - lib/store_attribute.rb
163
108
  - lib/store_attribute/active_record.rb
164
109
  - lib/store_attribute/active_record/store.rb
165
110
  - lib/store_attribute/active_record/type/typed_store.rb
166
111
  - lib/store_attribute/version.rb
167
- - spec/cases/store_attribute_spec.rb
168
- - spec/spec_helper.rb
169
- - spec/store_attribute/typed_store_spec.rb
170
- - spec/support/money_type.rb
171
- - spec/support/user.rb
172
- - store_attribute.gemspec
173
112
  homepage: http://github.com/palkan/store_attribute
174
113
  licenses:
175
114
  - MIT
@@ -194,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
133
  - !ruby/object:Gem::Version
195
134
  version: '0'
196
135
  requirements: []
197
- rubygems_version: 3.0.3
136
+ rubygems_version: 3.0.6
198
137
  signing_key:
199
138
  specification_version: 4
200
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,54 +0,0 @@
1
- require:
2
- - standard/cop/semantic_blocks
3
- - rubocop-md
4
-
5
- inherit_gem:
6
- standard: config/base.yml
7
-
8
- AllCops:
9
- Exclude:
10
- - 'bin/*'
11
- - 'tmp/**/*'
12
- - 'Gemfile'
13
- - 'vendor/**/*'
14
- - 'gemfiles/**/*'
15
- - 'bench/**/*'
16
- DisplayCopNames: true
17
- TargetRubyVersion: 2.4
18
-
19
- Standard/SemanticBlocks:
20
- Enabled: false
21
-
22
- Style/FrozenStringLiteralComment:
23
- Enabled: true
24
-
25
- Style/TrailingCommaInArrayLiteral:
26
- EnforcedStyleForMultiline: no_comma
27
-
28
- Style/TrailingCommaInHashLiteral:
29
- EnforcedStyleForMultiline: no_comma
30
-
31
- Layout/AlignParameters:
32
- EnforcedStyle: with_first_parameter
33
-
34
- Lint/Void:
35
- Exclude:
36
- - '**/*.md'
37
-
38
- # See https://github.com/rubocop-hq/rubocop/issues/4222
39
- Lint/AmbiguousBlockAssociation:
40
- Exclude:
41
- - 'spec/**/*'
42
- - '**/*.md'
43
-
44
- Lint/DuplicateMethods:
45
- Exclude:
46
- - '**/*.md'
47
-
48
- Naming/FileName:
49
- Exclude:
50
- - '**/*.md'
51
-
52
- Layout/InitialIndentation:
53
- Exclude:
54
- - 'CHANGELOG.md'
@@ -1,21 +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.4.1
14
- gemfile: gemfiles/rails5.gemfile
15
- - rvm: 2.6.2
16
- gemfile: gemfiles/rails6.gemfile
17
- - rvm: ruby-head
18
- gemfile: gemfiles/railsmaster.gemfile
19
- allow_failures:
20
- - rvm: ruby-head
21
- gemfile: gemfiles/railsmaster.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.1.0'
11
- end
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "rubocop/rake_task"
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
- RuboCop::RakeTask.new
9
-
10
- task default: [:rubocop, :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.1.0'
4
-
5
- gemspec path: '..'
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'rails', '6.0.0.beta3'
4
-
5
- gemspec path: '..'
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'rails', github: 'rails/rails'
4
-
5
- gemspec path: '..'
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe StoreAttribute do
6
- before do
7
- @connection = ActiveRecord::Base.connection
8
-
9
- @connection.transaction do
10
- @connection.create_table("users") do |t|
11
- t.jsonb :jparams, default: {}, null: false
12
- t.text :custom
13
- t.hstore :hdata, default: {}, null: false
14
- end
15
- end
16
-
17
- User.reset_column_information
18
- end
19
-
20
- after do
21
- @connection.drop_table "users", if_exists: true
22
- end
23
-
24
- let(:date) { Date.new(2019, 7, 17) }
25
- let(:default_date) { User::DEFAULT_DATE }
26
- let(:dynamic_date) { User::TODAY_DATE }
27
- let(:time) { DateTime.new(2015, 2, 14, 17, 0, 0) }
28
- let(:time_str) { "2015-02-14 17:00" }
29
- let(:time_str_utc) { "2015-02-14 17:00:00 UTC" }
30
-
31
- context "hstore" do
32
- it "typecasts on build" do
33
- user = User.new(visible: "t", login_at: time_str)
34
- expect(user.visible).to eq true
35
- expect(user).to be_visible
36
- expect(user.login_at).to eq time
37
- end
38
-
39
- it "typecasts on reload" do
40
- user = User.new(visible: "t", login_at: time_str)
41
- user.save!
42
- user = User.find(user.id)
43
-
44
- expect(user.visible).to eq true
45
- expect(user).to be_visible
46
- expect(user.login_at).to eq time
47
- end
48
-
49
- it "works with accessors" do
50
- user = User.new
51
- user.visible = false
52
- user.login_at = time_str
53
- user.save!
54
-
55
- user = User.find(user.id)
56
-
57
- expect(user.visible).to be false
58
- expect(user).not_to be_visible
59
- expect(user.login_at).to eq time
60
-
61
- ron = RawUser.find(user.id)
62
- expect(ron.hdata["visible"]).to eq "false"
63
- expect(ron.hdata["login_at"]).to eq time_str_utc
64
- end
65
-
66
- it "handles options" do
67
- expect { User.create!(ratio: 1024) }.to raise_error(RangeError)
68
- end
69
-
70
- it "YAML roundtrip" do
71
- user = User.create!(visible: "0", login_at: time_str)
72
- dumped = YAML.load(YAML.dump(user)) # rubocop:disable Security/YAMLLoad
73
-
74
- expect(dumped.visible).to be false
75
- expect(dumped.login_at).to eq time
76
- end
77
- end
78
-
79
- context "jsonb" do
80
- it "typecasts on build" do
81
- jamie = User.new(
82
- active: "true",
83
- salary: 3.1999,
84
- birthday: "2000-01-01"
85
- )
86
- expect(jamie).to be_active
87
- expect(jamie.salary).to eq 3
88
- expect(jamie.birthday).to eq Date.new(2000, 1, 1)
89
- expect(jamie.jparams["birthday"]).to eq Date.new(2000, 1, 1)
90
- expect(jamie.jparams["active"]).to eq true
91
- end
92
-
93
- it "typecasts on reload" do
94
- jamie = User.create!(jparams: {"active" => "1", "birthday" => "01/01/2000", "salary" => "3.14"})
95
- jamie = User.find(jamie.id)
96
-
97
- expect(jamie).to be_active
98
- expect(jamie.salary).to eq 3
99
- expect(jamie.birthday).to eq Date.new(2000, 1, 1)
100
- expect(jamie.jparams["birthday"]).to eq Date.new(2000, 1, 1)
101
- expect(jamie.jparams["active"]).to eq true
102
- end
103
-
104
- it "works with accessors" do
105
- john = User.new
106
- john.active = 1
107
-
108
- expect(john).to be_active
109
- expect(john.jparams["active"]).to eq true
110
-
111
- john.jparams = {active: "true", salary: "123.123", birthday: "01/01/2012"}
112
- expect(john).to be_active
113
- expect(john.birthday).to eq Date.new(2012, 1, 1)
114
- expect(john.salary).to eq 123
115
-
116
- john.save!
117
-
118
- ron = RawUser.find(john.id)
119
- expect(ron.jparams["active"]).to eq true
120
- expect(ron.jparams["birthday"]).to eq "2012-01-01"
121
- expect(ron.jparams["salary"]).to eq 123
122
- end
123
-
124
- it "re-typecast old data" do
125
- jamie = User.create!
126
- User.update_all(
127
- "jparams = '{"\
128
- '"active":"1",'\
129
- '"salary":"12.02"'\
130
- "}'::jsonb"
131
- )
132
-
133
- jamie = User.find(jamie.id)
134
- expect(jamie).to be_active
135
- expect(jamie.salary).to eq 12
136
-
137
- jamie.save!
138
-
139
- ron = RawUser.find(jamie.id)
140
- expect(ron.jparams["active"]).to eq true
141
- expect(ron.jparams["salary"]).to eq 12
142
- end
143
- end
144
-
145
- context "custom types" do
146
- it "typecasts on build" do
147
- user = User.new(price: "$1")
148
- expect(user.price).to eq 100
149
- end
150
-
151
- it "typecasts on reload" do
152
- jamie = User.create!(custom: {price: "$12"})
153
- expect(jamie.reload.price).to eq 1200
154
-
155
- jamie = User.find(jamie.id)
156
-
157
- expect(jamie.price).to eq 1200
158
- end
159
- end
160
-
161
- context "store subtype" do
162
- it "typecasts on build" do
163
- user = User.new(inner_json: {x: 1})
164
- expect(user.inner_json).to eq("x" => 1)
165
- end
166
-
167
- it "typecasts on update" do
168
- user = User.new
169
- user.update!(inner_json: {x: 1})
170
- expect(user.inner_json).to eq("x" => 1)
171
-
172
- expect(user.reload.inner_json).to eq("x" => 1)
173
- end
174
-
175
- it "typecasts on reload" do
176
- jamie = User.create!(inner_json: {x: 1})
177
- jamie = User.find(jamie.id)
178
- expect(jamie.inner_json).to eq("x" => 1)
179
- end
180
- end
181
-
182
- context "default option" do
183
- it "should init the field after an object is created" do
184
- jamie = User.new
185
- expect(jamie.static_date).to eq(default_date)
186
- end
187
-
188
- it "should not affect explicit initialization" do
189
- jamie = User.new(static_date: date)
190
- expect(jamie.static_date).to eq(date)
191
- end
192
-
193
- it "should not affect explicit nil initialization" do
194
- jamie = User.new(static_date: nil)
195
- expect(jamie.static_date).to be_nil
196
- end
197
-
198
- it "should handle a static value" do
199
- jamie = User.create!
200
- jamie = User.find(jamie.id)
201
- expect(jamie.static_date).to eq(default_date)
202
- end
203
-
204
- it "should handle a lambda" do
205
- jamie = User.create!
206
- jamie = User.find(jamie.id)
207
- expect(jamie.dynamic_date).to eq(dynamic_date)
208
- end
209
-
210
- it "should handle nil" do
211
- jamie = User.create!
212
- jamie = User.find(jamie.id)
213
- expect(jamie.empty_date).to be_nil
214
- end
215
- end
216
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
- $LOAD_PATH.unshift(File.dirname(__FILE__))
5
-
6
- if ENV["COVER"]
7
- require "simplecov"
8
- SimpleCov.root File.join(File.dirname(__FILE__), "..")
9
- SimpleCov.start
10
- end
11
-
12
- require "rspec"
13
- require "pry-byebug"
14
- require "active_record"
15
- require "pg"
16
- require "store_attribute"
17
-
18
- RAILS_5_1 = ActiveRecord.version.release >= Gem::Version.new("5.1.0")
19
-
20
- ActiveRecord::Base.establish_connection(
21
- adapter: "postgresql",
22
- database: "store_attribute_test"
23
- )
24
- connection = ActiveRecord::Base.connection
25
-
26
- unless connection.extension_enabled?("hstore")
27
- connection.enable_extension "hstore"
28
- connection.commit_db_transaction
29
- end
30
-
31
- connection.reconnect!
32
-
33
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
34
-
35
- RSpec.configure do |config|
36
- config.mock_with :rspec
37
-
38
- config.filter_run_when_matching :focus
39
-
40
- config.example_status_persistence_file_path = "tmp/rspec_examples.txt"
41
-
42
- if config.files_to_run.one?
43
- config.default_formatter = "doc"
44
- end
45
- end
@@ -1,240 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe ActiveRecord::Type::TypedStore do
6
- let(:json_type) { ActiveRecord::Type::Serialized.new(ActiveRecord::Type::Text.new, ActiveRecord::Coders::JSON) }
7
-
8
- context "with json store" do
9
- subject { described_class.new(json_type) }
10
-
11
- describe "#cast" do
12
- it "without key types", :aggregate_failures do
13
- expect(subject.cast([1, 2])).to eq [1, 2]
14
- expect(subject.cast("a" => "b")).to eq("a" => "b")
15
- end
16
-
17
- it "with type keys" do
18
- subject.add_typed_key("date", :date)
19
-
20
- date = ::Date.new(2016, 6, 22)
21
- expect(subject.cast(date: "2016-06-22")).to eq("date" => date)
22
- end
23
-
24
- it "with default" do
25
- date = ::Date.new(2016, 6, 22)
26
- subject.add_typed_key("date", :date, default: date)
27
-
28
- expect(subject.cast({})).to eq("date" => date)
29
- end
30
-
31
- it "with dynamic default" do
32
- date = ::Date.new(2016, 6, 22)
33
- subject.add_typed_key("date", :date, default: -> { date })
34
-
35
- expect(subject.cast({})).to eq("date" => date)
36
- end
37
-
38
- it "with default and explicit nil" do
39
- date = ::Date.new(2016, 6, 22)
40
- subject.add_typed_key("date", :date, default: date)
41
- nil_date = {"date" => nil}
42
-
43
- expect(subject.cast(nil_date)).to eq(nil_date)
44
- end
45
-
46
- it "with default and existing value" do
47
- default = ::Date.new(2016, 6, 22)
48
- expected = ::Date.new(2019, 7, 17)
49
- subject.add_typed_key("date", :date, default: default)
50
- date = {"date" => expected}
51
-
52
- expect(subject.cast("date" => expected.to_s)).to eq(date)
53
- end
54
- end
55
-
56
- describe "#deserialize" do
57
- it "without key types", :aggregate_failures do
58
- expect(subject.deserialize("[1,2]")).to eq [1, 2]
59
- expect(subject.deserialize('{"a":"b"}')).to eq("a" => "b")
60
- end
61
-
62
- it "with type keys" do
63
- subject.add_typed_key("date", :date)
64
-
65
- date = ::Date.new(2016, 6, 22)
66
- expect(subject.deserialize('{"date":"2016-06-22"}')).to eq("date" => date)
67
- end
68
-
69
- it "with default" do
70
- date = ::Date.new(2016, 6, 22)
71
- subject.add_typed_key("date", :date, default: date)
72
-
73
- expect(subject.deserialize("{}")).to eq("date" => date)
74
- end
75
-
76
- it "with dynamic default" do
77
- date = ::Date.new(2016, 6, 22)
78
- subject.add_typed_key("date", :date, default: -> { date })
79
-
80
- expect(subject.deserialize("{}")).to eq("date" => date)
81
- end
82
-
83
- it "with default and explicit nil" do
84
- date = ::Date.new(2016, 6, 22)
85
- subject.add_typed_key("date", :date, default: date)
86
- s11n = {"date" => nil}.to_json
87
-
88
- expect(subject.deserialize(s11n)).to eq("date" => nil)
89
- end
90
-
91
- it "with default and existing value" do
92
- default = ::Date.new(2016, 6, 22)
93
- expected = ::Date.new(2019, 7, 17)
94
- subject.add_typed_key("date", :date, default: default)
95
- s11n = {"date" => expected}.to_json
96
-
97
- expect(subject.deserialize(s11n)).to eq("date" => expected)
98
- end
99
- end
100
-
101
- describe "#serialize" do
102
- it "without key types", :aggregate_failures do
103
- expect(subject.serialize([1, 2])).to eq "[1,2]"
104
- expect(subject.serialize("a" => "b")).to eq '{"a":"b"}'
105
- end
106
-
107
- it "with type keys" do
108
- subject.add_typed_key("date", :date)
109
-
110
- date = ::Date.new(2016, 6, 22)
111
- expect(subject.serialize(date: date)).to eq '{"date":"2016-06-22"}'
112
- end
113
-
114
- it "with type key with option" do
115
- subject.add_typed_key("val", :integer, limit: 1)
116
-
117
- expect { subject.serialize(val: 1024) }.to raise_error(RangeError)
118
- end
119
-
120
- it "with type key and a default" do
121
- date = ::Date.new(2016, 6, 22)
122
- subject.add_typed_key("date", :date, default: date)
123
- expect(subject.serialize({})).to eq({date: date}.to_json)
124
- end
125
-
126
- it "with type key and a dynamic default" do
127
- date = ::Date.today
128
- default = -> { date }
129
- subject.add_typed_key("date", :date, default: default)
130
- expect(subject.serialize({})).to eq({date: date}.to_json)
131
- end
132
-
133
- it "with a symbolic type key" do
134
- date = ::Date.new(2016, 6, 22)
135
- subject.add_typed_key(:date, :date, default: date)
136
- expect(subject.serialize({})).to eq({date: date}.to_json)
137
- end
138
- end
139
-
140
- describe ".create_from_type" do
141
- it "creates with valid types", :aggregate_failures do
142
- type = described_class.create_from_type(json_type, "date", :date)
143
- new_type = described_class.create_from_type(type, "val", :integer)
144
-
145
- date = ::Date.new(2016, 6, 22)
146
-
147
- expect(type.cast(date: "2016-06-22", val: "1.2")).to eq("date" => date, "val" => "1.2")
148
- expect(new_type.cast(date: "2016-06-22", val: "1.2")).to eq("date" => date, "val" => 1)
149
- end
150
-
151
- it "accepts default", :aggregate_failures do
152
- default = 1
153
- type = described_class.create_from_type(json_type, "val", :date, default: default)
154
-
155
- expect(type.cast({})).to eq("val" => default)
156
- end
157
-
158
- it "accepts dynamic default", :aggregate_failures do
159
- default = 1
160
- type = described_class.create_from_type(json_type, "val", :date, default: -> { default })
161
-
162
- expect(type.cast({})).to eq("val" => default)
163
- end
164
- end
165
- end
166
-
167
- context "with yaml coder" do
168
- if RAILS_5_1
169
- let(:yaml_type) do
170
- ActiveRecord::Type::Serialized.new(
171
- ActiveRecord::Type::Text.new,
172
- ActiveRecord::Store::IndifferentCoder.new(
173
- "test",
174
- ActiveRecord::Coders::YAMLColumn.new("test", Hash)
175
- )
176
- )
177
- end
178
- else
179
- let(:yaml_type) do
180
- ActiveRecord::Type::Serialized.new(
181
- ActiveRecord::Type::Text.new,
182
- ActiveRecord::Store::IndifferentCoder.new(
183
- ActiveRecord::Coders::YAMLColumn.new(Hash)
184
- )
185
- )
186
- end
187
- end
188
-
189
- let(:subject) { described_class.new(yaml_type) }
190
-
191
- it "works", :aggregate_failures do
192
- subject.add_typed_key("date", :date)
193
-
194
- date = ::Date.new(2016, 6, 22)
195
-
196
- expect(subject.cast(date: "2016-06-22")).to eq("date" => date)
197
- expect(subject.cast("date" => "2016-06-22")).to eq("date" => date)
198
- expect(subject.deserialize("---\n:date: 2016-06-22\n")).to eq("date" => date)
199
- expect(subject.deserialize("---\ndate: 2016-06-22\n")).to eq("date" => date)
200
- expect(subject.serialize(date: date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
201
- expect(subject.serialize("date" => date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
202
- end
203
-
204
- it "uses default" do
205
- default = ::Date.new(2019, 7, 17)
206
- subject.add_typed_key("date", :date, default: default)
207
-
208
- expect(subject.deserialize("---\n")).to eq("date" => default)
209
- end
210
-
211
- it "uses dynamic default" do
212
- default = -> { ::Date.new(2019, 7, 17) }
213
- subject.add_typed_key("date", :date, default: default)
214
-
215
- expect(subject.deserialize("---\n")).to eq("date" => default.call)
216
- end
217
-
218
- it "keeps explicit nil" do
219
- default = ::Date.new(2019, 7, 17)
220
- subject.add_typed_key("date", :date, default: default)
221
-
222
- expect(subject.deserialize("---\ndate: null\n")).to eq("date" => nil)
223
- end
224
-
225
- it "keeps existing value" do
226
- default = ::Date.new(2019, 7, 17)
227
- date = ::Date.new(2016, 6, 22)
228
- subject.add_typed_key("date", :date, default: default)
229
-
230
- expect(subject.deserialize("---\ndate: #{date}\n")).to eq("date" => date)
231
- end
232
-
233
- it "with a default" do
234
- default = ::Date.new(2019, 7, 17)
235
- subject.add_typed_key("date", :date, default: default)
236
- expected = "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2019-07-17\n"
237
- expect(subject.serialize({})).to eq(expected)
238
- end
239
- end
240
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class MoneyType < ActiveRecord::Type::Integer
4
- def cast(value)
5
- if !value.is_a?(Numeric) && value.include?("$")
6
- price_in_dollars = value.delete("$").to_f
7
- super(price_in_dollars * 100)
8
- else
9
- super
10
- end
11
- end
12
- end
13
-
14
- ActiveRecord::Type.register(:money_type, MoneyType)
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RawUser < ActiveRecord::Base
4
- self.table_name = "users"
5
- end
6
-
7
- class User < ActiveRecord::Base
8
- DEFAULT_DATE = ::Date.new(2019, 7, 17)
9
- TODAY_DATE = ::Date.today
10
-
11
- store_accessor :jparams, :version, active: :boolean, salary: :integer
12
- store_attribute :jparams, :birthday, :date
13
- store_attribute :jparams, :static_date, :date, default: DEFAULT_DATE
14
- store_attribute :jparams, :dynamic_date, :date, default: -> { TODAY_DATE }
15
- store_attribute :jparams, :empty_date, :date, default: nil
16
- store_attribute :jparams, :inner_json, :json
17
-
18
- store :custom, accessors: [price: :money_type]
19
-
20
- store_accessor :hdata, visible: :boolean
21
-
22
- store_attribute :hdata, :ratio, :integer, limit: 1
23
- store_attribute :hdata, :login_at, :datetime
24
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $:.push File.expand_path("../lib", __FILE__)
4
-
5
- # Maintain your gem's version:
6
- require "store_attribute/version"
7
-
8
- # Describe your gem and declare its dependencies:
9
- Gem::Specification.new do |s|
10
- s.name = "store_attribute"
11
- s.version = StoreAttribute::VERSION
12
- s.authors = ["palkan"]
13
- s.email = ["dementiev.vm@gmail.com"]
14
- s.homepage = "http://github.com/palkan/store_attribute"
15
- s.summary = "ActiveRecord extension which adds typecasting to store accessors"
16
- s.description = "ActiveRecord extension which adds typecasting to store accessors"
17
- s.license = "MIT"
18
-
19
- s.files = `git ls-files`.split($/)
20
- s.require_paths = ["lib"]
21
-
22
- s.required_ruby_version = ">= 2.4.0"
23
-
24
- s.metadata = {
25
- "bug_tracker_uri" => "http://github.com/palkan/store_attribute/issues",
26
- "changelog_uri" => "https://github.com/palkan/store_attribute/blob/master/CHANGELOG.md",
27
- "documentation_uri" => "http://github.com/palkan/store_attribute",
28
- "homepage_uri" => "http://github.com/palkan/store_attribute",
29
- "source_code_uri" => "http://github.com/palkan/store_attribute"
30
- }
31
-
32
- s.add_runtime_dependency "activerecord", ">= 5.0"
33
-
34
- s.add_development_dependency "pg", ">= 0.18"
35
- s.add_development_dependency "rake", ">= 10.1"
36
- s.add_development_dependency "simplecov", ">= 0.3.8"
37
- s.add_development_dependency "pry-byebug"
38
- s.add_development_dependency "rubocop", "~> 0.65.0"
39
- s.add_development_dependency "rubocop-md", "~> 0.2"
40
- s.add_development_dependency "standard", "~> 0.0.36"
41
- s.add_development_dependency "rspec", ">= 3.5.0"
42
- end