activejsonmodel 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +79 -0
- data/lib/active_json_model.rb +8 -0
- data/lib/activejsonmodel/active_record_encrypted_type.rb +72 -0
- data/lib/activejsonmodel/active_record_type.rb +84 -0
- data/lib/activejsonmodel/after_load_callback.rb +35 -0
- data/lib/activejsonmodel/array.rb +699 -0
- data/lib/activejsonmodel/json_attribute.rb +64 -0
- data/lib/activejsonmodel/model.rb +597 -0
- data/lib/activejsonmodel/utils.rb +23 -0
- data/lib/activejsonmodel/version.rb +5 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4bc1e18ab1cb9a6672dc6ab19a0a05340f4761b5cb51ef626d36800846f85a5c
|
4
|
+
data.tar.gz: 847077d254ef430b85ee5853f8d882e0df84f12b34494d1d47868aa32198989c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79b717de46de263cc15a03ca0bad9c636bf12458f037078c66b61bcf7c31fbead8106b4506b54731cf44d9a1a267a62e062f900ca37a0d30e79520f9b44e1ee3
|
7
|
+
data.tar.gz: 02cdb543f99326a32c4645898cc99bded4e3136a9b4b3e2d9b09f61792f6761db091ea52f6acf52ec8b4a8f0d1ad4575ddfa74e29ba6e561ea25251f3b1d4630
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Ryan Morlok
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# activejsonmodel
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/activejsonmodel.svg)](https://rubygems.org/gems/activejsonmodel)
|
4
|
+
[![Circle](https://circleci.com/gh/rmorlok/activejsonmodel/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/rmorlok/activejsonmodel?branch=main)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/rmorlok/activejsonmodel/badges/gpa.svg)](https://codeclimate.com/github/rmorlok/activejsonmodel)
|
6
|
+
|
7
|
+
TODO: Description of this gem goes here.
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
- [Quick start](#quick-start)
|
12
|
+
- [Support](#support)
|
13
|
+
- [License](#license)
|
14
|
+
- [Code of conduct](#code-of-conduct)
|
15
|
+
- [Contribution guide](#contribution-guide)
|
16
|
+
|
17
|
+
## Quick start
|
18
|
+
|
19
|
+
```
|
20
|
+
$ gem install activejsonmodel
|
21
|
+
```
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require "active_json_model"
|
25
|
+
```
|
26
|
+
|
27
|
+
## Support
|
28
|
+
|
29
|
+
If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/rmorlok/activejsonmodel/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
|
30
|
+
|
31
|
+
## License
|
32
|
+
|
33
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
34
|
+
|
35
|
+
## Code of conduct
|
36
|
+
|
37
|
+
Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
38
|
+
|
39
|
+
## Contribution guide
|
40
|
+
|
41
|
+
Pull requests are welcome!
|
42
|
+
|
43
|
+
### Development Setup
|
44
|
+
|
45
|
+
```bash
|
46
|
+
brew install rbenv
|
47
|
+
```
|
48
|
+
|
49
|
+
Add the following to `~/.zshrc`:
|
50
|
+
|
51
|
+
```bash
|
52
|
+
RBENV=`which rbenv`
|
53
|
+
if [ $RBENV ] ; then
|
54
|
+
export PATH=$HOME/.rbenv/bin:$PATH
|
55
|
+
eval "$(rbenv init -)"
|
56
|
+
fi
|
57
|
+
```
|
58
|
+
|
59
|
+
Reload the `~/.zshrc`.
|
60
|
+
|
61
|
+
Install development ruby version:
|
62
|
+
|
63
|
+
```bash
|
64
|
+
cat .ruby-version | xargs rbenv install
|
65
|
+
```
|
66
|
+
|
67
|
+
Exit and re-enter the directory to make sure the current version of ruby is used. Install dependencies:
|
68
|
+
|
69
|
+
```bash
|
70
|
+
bundle install
|
71
|
+
```
|
72
|
+
|
73
|
+
### Running Tests
|
74
|
+
|
75
|
+
Active JSON Model uses [minitest](https://github.com/minitest/minitest). To run tests, use rake:
|
76
|
+
|
77
|
+
```bash
|
78
|
+
rake test
|
79
|
+
```
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Only available if the symmetric-encryption gem is installed
|
4
|
+
# https://github.com/reidmorrison/symmetric-encryption
|
5
|
+
if Gem.find_files("symmetric-encryption").any? &&
|
6
|
+
Gem.find_files("active_record").any?
|
7
|
+
|
8
|
+
require "symmetric-encryption"
|
9
|
+
require 'active_support'
|
10
|
+
require_relative './active_record_type'
|
11
|
+
|
12
|
+
module ActiveJsonModel
|
13
|
+
# Allows instances of ActiveJsonModels to be serialized JSONB columns for ActiveRecord models.
|
14
|
+
#
|
15
|
+
# class Credentials < ::ActiveJsonModel
|
16
|
+
# def self.encrypted_attribute_type
|
17
|
+
# ActiveRecordEncryptedType.new(Credentials)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class Integration < ActiveRecord::Base
|
22
|
+
# attribute :credentials, Credentials.encrypted_attribute_type
|
23
|
+
# end
|
24
|
+
class ActiveRecordEncryptedType < ::ActiveJsonModel::ActiveRecordType
|
25
|
+
def type
|
26
|
+
:string
|
27
|
+
end
|
28
|
+
|
29
|
+
def cast(value)
|
30
|
+
if value.is_a?(@clazz)
|
31
|
+
value
|
32
|
+
elsif value.is_a?(::Array)
|
33
|
+
@clazz.load(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def deserialize(value)
|
38
|
+
if String === value
|
39
|
+
decoded = SymmetricEncryption.decrypt(value, type: :json) rescue nil
|
40
|
+
@clazz.load(decoded)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def serialize(value)
|
47
|
+
case value
|
48
|
+
when @clazz
|
49
|
+
::ActiveSupport::JSON.encode(@clazz.dump(value))
|
50
|
+
when Array, Hash
|
51
|
+
::ActiveSupport::JSON.encode(value)
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Override to handle issues comparing hashes encoded as strings, where the actual order doesn't matter.
|
58
|
+
def changed_in_place?(raw_old_value, new_value)
|
59
|
+
if raw_old_value.nil? || new_value.nil?
|
60
|
+
raw_old_value == new_value
|
61
|
+
else
|
62
|
+
# Decode is necessary because postgres can change the order of hashes. Round-tripping on the new value side
|
63
|
+
# is to handle any dates that might be rendered as strings.
|
64
|
+
decoded_raw = ::ActiveSupport::JSON.decode(raw_old_value)
|
65
|
+
round_tripped_new = ::ActiveSupport::JSON.decode(new_value.class.dump(new_value))
|
66
|
+
|
67
|
+
decoded_raw != round_tripped_new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Only available if the active record is installed, generally in a rails environment
|
4
|
+
if Gem.find_files("active_record").any?
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_support'
|
8
|
+
|
9
|
+
module ActiveJsonModel
|
10
|
+
# Allows instances of ActiveJsonModels to be serialized JSONB columns for ActiveRecord models.
|
11
|
+
#
|
12
|
+
# class Credentials < ::ActiveJsonModel
|
13
|
+
# def self.attribute_type
|
14
|
+
# ActiveRecordType.new(Credentials)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class Integration < ActiveRecord::Base
|
19
|
+
# attribute :credentials, Credentials.attribute_type
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# This is based on:
|
23
|
+
# https://jetrockets.pro/blog/rails-5-attributes-api-value-objects-and-jsonb
|
24
|
+
class ActiveRecordType < ::ActiveRecord::Type::Value
|
25
|
+
include ::ActiveModel::Type::Helpers::Mutable
|
26
|
+
|
27
|
+
# Create an instance bound to a ActiveJsonModel class.
|
28
|
+
#
|
29
|
+
# e.g.
|
30
|
+
# class Credentials < ::ActiveJsonModel; end
|
31
|
+
# #...
|
32
|
+
# return ActiveRecordType.new(Credentials)
|
33
|
+
def initialize(clazz)
|
34
|
+
@clazz = clazz
|
35
|
+
end
|
36
|
+
|
37
|
+
def type
|
38
|
+
:jsonb
|
39
|
+
end
|
40
|
+
|
41
|
+
def cast(value)
|
42
|
+
if value.is_a?(@clazz)
|
43
|
+
value
|
44
|
+
elsif value.is_a?(::Array)
|
45
|
+
@clazz.load(value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def deserialize(value)
|
50
|
+
if String === value
|
51
|
+
decoded = ::ActiveSupport::JSON.decode(value) rescue nil
|
52
|
+
@clazz.load(decoded)
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def serialize(value)
|
59
|
+
case value
|
60
|
+
when @clazz
|
61
|
+
::ActiveSupport::JSON.encode(@clazz.dump(value))
|
62
|
+
when Array, Hash
|
63
|
+
::ActiveSupport::JSON.encode(value)
|
64
|
+
else
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Override to handle issues comparing hashes encoded as strings, where the actual order doesn't matter.
|
70
|
+
def changed_in_place?(raw_old_value, new_value)
|
71
|
+
if raw_old_value.nil? || new_value.nil?
|
72
|
+
raw_old_value == new_value
|
73
|
+
else
|
74
|
+
# Decode is necessary because postgres can change the order of hashes. Round-tripping on the new value side
|
75
|
+
# is to handle any dates that might be rendered as strings.
|
76
|
+
decoded_raw = ::ActiveSupport::JSON.decode(raw_old_value)
|
77
|
+
round_tripped_new = ::ActiveSupport::JSON.decode(new_value.class.dump(new_value))
|
78
|
+
|
79
|
+
decoded_raw != round_tripped_new
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJsonModel
|
4
|
+
class AfterLoadCallback
|
5
|
+
attr_reader :method_name
|
6
|
+
attr_reader :block
|
7
|
+
|
8
|
+
# Data holder for an after-load callback
|
9
|
+
#
|
10
|
+
# @param method_name [Symbol] the name of method to be invoked as a callback
|
11
|
+
# @param block [Proc] block to be executed as the callback
|
12
|
+
def initialize(method_name:, block:)
|
13
|
+
raise "ActiveJsonModel after load callback must either be a block or a method name" if method_name && block
|
14
|
+
raise "ActiveJsonModel after load callback must either specify a block or method name" unless method_name || block
|
15
|
+
|
16
|
+
@method_name = method_name&.to_sym
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
|
20
|
+
# Invoke this callback on <code>on_object</code> the object just loaded from JSON
|
21
|
+
#
|
22
|
+
# @param on_object [Object] the object just loaded from JSON
|
23
|
+
def invoke(on_object)
|
24
|
+
if method_name
|
25
|
+
on_object.send(method_name)
|
26
|
+
else
|
27
|
+
if block.arity == 1
|
28
|
+
block.call(on_object)
|
29
|
+
else
|
30
|
+
block.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|