store_attribute 0.6.0 → 0.7.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 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