json_schematize 0.5.0 → 0.6.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: 8d0fba5a93c9300806e91b5d13da3d16974a72d7598d3b180bd4f9ab13b711c1
4
- data.tar.gz: 7361e6e1b2994abd945ab62f90f6d3d7a17fd5e8a51ba11f78525316e2062436
3
+ metadata.gz: 7db5a295c8de919a0eb55d2894094d8c5c97f932b0955495c31ed762e84133f8
4
+ data.tar.gz: a8dc99a3affdf8027d9ec4bde5e689e84b18d1571ba126e54cb806d484ad8fb2
5
5
  SHA512:
6
- metadata.gz: a0c5362513008667000c309259dfb21e61835eb150f095f18bfdd84cba21e391d3d44de2bd2d72ed7fffc34963122c029d4032e8ecc5d3459d1f5818dd935f5c
7
- data.tar.gz: 25a59ae832bd2eb65bf92dabe3dac3989f34ad0b46d49e554ec19769b83549b71a2387237b45ee6a7c9e3472662db74c197029ecae2bda1b1ce6b292fdaf79b1
6
+ metadata.gz: 4de0766c2fb072d96908c1cb66e7611c79db5635cfc046b51d90ad40a8d732d0fe086e62e1b243b0813d90b48e9607198c854624544e2e05b7bbd8a59dd7fb2b
7
+ data.tar.gz: ce9fa6fdda05e251c7ec45cbff00a22f7aba3ce28f31820f682da05676584defdf491540d5cddd17e26697d572665081781430be7dcaa87723d7466f975f2560
data/.circleci/config.yml CHANGED
@@ -8,6 +8,7 @@ orbs:
8
8
  common_envs: &common_envs
9
9
  environment:
10
10
  APP_BUNDLER_VERSION: "2.3.8"
11
+ REDIS_URL: "redis://127.0.0.1"
11
12
 
12
13
  executors:
13
14
  gem:
@@ -17,6 +18,8 @@ executors:
17
18
  type: string
18
19
  docker:
19
20
  - image: cimg/ruby:<< parameters.ruby-version >>
21
+ - image: circleci/redis:6.0
22
+
20
23
  <<: *common_envs
21
24
 
22
25
  commands:
@@ -146,6 +149,16 @@ jobs:
146
149
  command: |
147
150
  curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
148
151
  chmod +x ./cc-test-reporter
152
+ - run:
153
+ name: Waiting for Redis to be ready
154
+ command: |
155
+ for i in `seq 1 10`;
156
+ do
157
+ nc -z localhost 6379 && echo Success && exit 0
158
+ echo -n .
159
+ sleep 1
160
+ done
161
+ echo Failed waiting for Redis to be available && exit 1
149
162
  - run:
150
163
  name: Run and Report tests
151
164
  command: |
data/CHANGELOG.md CHANGED
@@ -7,10 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.1.0] - YYYY-MM-DD
10
+ ## [0.6.0] - 2021-04-01
11
11
 
12
12
  ### Added
13
- - New feature 1
13
+ - Caching Layer
14
14
  - New feature 2
15
15
 
16
16
  ### Changed
@@ -24,3 +24,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
24
  ### Fixed
25
25
  - Bug fix 1
26
26
  - Bug fix 2
27
+
28
+ [//]: # (Added, Changed, Removed, Fixed are the possible headers for each changlog version)
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
+ gem 'redis'
7
8
  gem 'faker'
8
9
  gem 'pry'
9
10
  gem 'rspec', '~> 3.0'
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schematize (0.5.0)
4
+ json_schematize (0.6.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  byebug (11.1.3)
10
10
  coderay (1.1.3)
11
- concurrent-ruby (1.1.9)
11
+ concurrent-ruby (1.1.10)
12
12
  diff-lcs (1.5.0)
13
13
  docile (1.4.0)
14
14
  faker (2.20.0)
@@ -23,6 +23,7 @@ GEM
23
23
  byebug (~> 11.0)
24
24
  pry (~> 0.13.0)
25
25
  rake (12.3.3)
26
+ redis (4.6.0)
26
27
  rspec (3.11.0)
27
28
  rspec-core (~> 3.11.0)
28
29
  rspec-expectations (~> 3.11.0)
@@ -32,7 +33,7 @@ GEM
32
33
  rspec-expectations (3.11.0)
33
34
  diff-lcs (>= 1.2.0, < 2.0)
34
35
  rspec-support (~> 3.11.0)
35
- rspec-mocks (3.11.0)
36
+ rspec-mocks (3.11.1)
36
37
  diff-lcs (>= 1.2.0, < 2.0)
37
38
  rspec-support (~> 3.11.0)
38
39
  rspec-support (3.11.0)
@@ -54,6 +55,7 @@ DEPENDENCIES
54
55
  pry
55
56
  pry-byebug
56
57
  rake (~> 12.0)
58
+ redis
57
59
  rspec (~> 3.0)
58
60
  rspec_junit_formatter
59
61
  simplecov
data/README.md CHANGED
@@ -110,6 +110,87 @@ class CustomClasses < JsonSchematize::Generator
110
110
  end
111
111
  ```
112
112
 
113
+ ### Caching Adapter
114
+
115
+ JsonSchematize is built to be schema for API results. But what happens when you dont expect the result to change? Introducing the caching layer. This layer lets you cache a `JsonSchematize` object that can be queried from later
116
+
117
+ **Note: This requires redis**
118
+
119
+ ```ruby
120
+ class CachedClass < JsonSchematize::Generator
121
+ include JsonSchematize::Cache
122
+ cache_options key: ->(instance_of_cached_class, cache_key_from_initialization) { "#{instance_of_cached_class.id}:#{cache_key_from_initialization}" }
123
+ cache_options ttl: 7.days.to_i
124
+
125
+ schema_default option: :dig_type, value: :string
126
+
127
+ add_field name: :id, type: Integer
128
+ end
129
+
130
+ params = { id: 1 }
131
+ CachedClass.new(cache_key: User.first.id, **params)
132
+ ###
133
+ params = { "id" => 1 }
134
+ CachedClass.new(params, cache_key: User.first.id)
135
+ ```
136
+
137
+ #### Instance methods for Cache
138
+ ```ruby
139
+ # optional cache_key added on initialization: Can be used to customize the cache entry for the instance
140
+ item = CachedClass.new(cache_key: User.first.id, **params)
141
+
142
+ # Update the cached item -- Note: This will overwrite the previous cached item IFF the `__cache_key__` remains the same. This is not gaurenteed
143
+ # Optional Param: with_delete, Default true -- Will attempt to delete the original object
144
+ item.__update_cache_item__
145
+
146
+ # Manually delete the cached entry
147
+ item.__clear_entry__!
148
+
149
+ # Cache key for the item
150
+ item.__cache_key__
151
+ ```
152
+
153
+ #### Class methods for Cache
154
+ ```ruby
155
+ # Retrieve all cached items for the class. Returns an array of CachedClass objects. Only objects that have not expired via TTL
156
+ # Optional: key_includes: "string_expected_in_cache", default is nil and return everthing
157
+ CachedClass.cached_items
158
+
159
+ # Retrieves all valid object keys from the cache
160
+ CachedClass.cached_keys
161
+
162
+ # Clears all cached items for the given class
163
+ CachedClass.clear_cache!
164
+
165
+ # manually clear objects that have expired
166
+ CachedClass.clear_unscored_items!
167
+ ```
168
+ ### Cache options
169
+ ```ruby
170
+ # [Required] false; [Expect] Proc; [Return] String to be used as instance key; [Default] key will be the hash of the object
171
+ cache_options key: ->(instance_of_class, custom_key) { }
172
+
173
+ # [Required] false; [Expect] String; [Default] ENV["CACHE_LAYER_REDIS_URL"] || ENV["REDIS_URL"]
174
+ cache_options redis_url: _redis_url_value
175
+
176
+ # [Required] false; [Expect] Redis Object or Proc that returns value of Redis Client; [Default] Redis.new(url: redis_url)
177
+ cache_options redis_client: redis_client
178
+
179
+ # [Required] false; [Expect] Object that plays with to_s and has no spaces; [Default] Full class name downcased
180
+ cache_options cache_namespace: cache_namespace
181
+
182
+ # [Required] false; [Expect] Integer in seconds; [Default] 1 day
183
+ cache_options ttl: (60 * 60)
184
+
185
+ # [Required] false; [Expect] Boolean; [Default] true
186
+ # Update the cache when a value has been changed manually
187
+ cache_options update_on_change: true
188
+
189
+ # [Required] false; [Expect] Float; [Default] 0.8
190
+ # Expected value to be between 0 and 1. The sample rate that the class will clear oldcache values on retreival
191
+ cache_options stochastic_cache_bust: 0.8
192
+ ```
193
+
113
194
  ## Development
114
195
 
115
196
  This gem can be developed against local machine or while using docker. Simpleified Docker commands can be found in the `Makefile` or execute `make help`
data/docker-compose.yml CHANGED
@@ -10,6 +10,13 @@ services:
10
10
  - .:/gem
11
11
  - ..:/local
12
12
  - bundle-cache:/usr/local/bundle:delegated
13
-
13
+ environment:
14
+ REDIS_URL: redis://redis
15
+ links:
16
+ - redis
17
+ redis:
18
+ image: redis
19
+ expose:
20
+ - 6379
14
21
  volumes:
15
22
  bundle-cache:
@@ -0,0 +1,81 @@
1
+ module JsonSchematize
2
+ module Cache
3
+ module ClassMethods
4
+ DEFAULT_ONE_MIN = 60 * 60
5
+ DEFAULT_ONE_HOUR = DEFAULT_ONE_MIN * 60
6
+ DEFAULT_ONE_DAY = DEFAULT_ONE_HOUR * 24
7
+ DEFAULT_URL = ENV["CACHE_LAYER_REDIS_URL"] || ENV["REDIS_URL"]
8
+ DEFAULTS = {
9
+ redis_url: DEFAULT_URL,
10
+ ttl: DEFAULT_ONE_DAY,
11
+ key: ->(val, _custom_key) { val.hash },
12
+ update_on_change: true,
13
+ redis_client: ->() { ::Redis.new(url: DEFAULT_URL) },
14
+ stochastic_cache_bust: 0.8,
15
+ }
16
+
17
+ def cache_options(key: nil, redis_url: nil, redis_client: nil, cache_namespace: nil, ttl: nil, update_on_change: nil, stochastic_cache_bust: nil)
18
+ cache_configuration[:key] = key if key
19
+ cache_configuration[:ttl] = ttl if ttl
20
+ cache_configuration[:stochastic_cache_bust] = stochastic_cache_bust if stochastic_cache_bust
21
+ cache_configuration[:update_on_change] = update_on_change if update_on_change
22
+ cache_namespace = cache_configuration[:cache_namespace] = cache_namespace if cache_namespace
23
+
24
+ self.redis_client = cache_configuration[:redis_client] = redis_client if redis_client
25
+ self.redis_url = cache_configuration[:redis_url] = redis_url if redis_url
26
+ end
27
+
28
+ def cache_namespace
29
+ cache_configuration[:cache_namespace] ||= "jss:#{self.name.downcase}"
30
+ end
31
+
32
+ def cache_namespace=(namespace)
33
+ cache_configuration[:cache_namespace] = namespace
34
+ end
35
+
36
+ def cache_configuration
37
+ @cache_configuration ||= DEFAULTS.clone
38
+ end
39
+
40
+ def redis_client=(client)
41
+ cache_configuration[:redis_client] = client
42
+ end
43
+
44
+ def redis_url=(url)
45
+ cache_configuration[:redis_url] = url
46
+ cache_configuration[:redis_client] = ::Redis.new(url: url)
47
+ end
48
+
49
+ def redis_client
50
+ cache_configuration[:redis_client].is_a?(Proc) ? cache_configuration[:redis_client].call : cache_configuration[:redis_client]
51
+ end
52
+
53
+ def cached_keys
54
+ max_length = Time.now.to_i + cache_configuration[:ttl].to_i + 10
55
+ redis_client.zrangebyscore(cache_namespace, Time.now.to_i, "+inf")
56
+ end
57
+
58
+ def cached_items(key_includes: nil)
59
+ clear_unscored_items! if rand > cache_configuration[:stochastic_cache_bust]
60
+
61
+ cached_keys.map do |key|
62
+ if key_includes
63
+ next unless key.include?(key_includes)
64
+ end
65
+
66
+ serialized_string = redis_client.get(key)
67
+ Marshal.load(serialized_string)
68
+ end
69
+ end
70
+
71
+ def clear_cache!
72
+ redis_client.unlink(*cached_keys) if cached_keys.length > 0
73
+ redis_client.unlink(cache_namespace)
74
+ end
75
+
76
+ def clear_unscored_items!
77
+ redis_client.zremrangebyscore(cache_namespace, "-inf", Time.now.to_i)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,36 @@
1
+ module JsonSchematize
2
+ module Cache
3
+ module InstanceMethods
4
+ def initialize(stringified_params = nil, cache_key: nil, skip_cache_update: false, raise_on_error: true, **params)
5
+ super(stringified_params, raise_on_error: raise_on_error, **params)
6
+
7
+ @__incoming_cache_key__ = cache_key
8
+ if @values_assigned
9
+ __update_cache_item__(with_delete: false) unless skip_cache_update
10
+ end
11
+ end
12
+
13
+ def __update_cache_item__(with_delete: true)
14
+ __clear_entry__! if with_delete # needs to get done first in the event the cache_key changes
15
+ client = self.class.redis_client
16
+ ttl = self.class.cache_configuration[:ttl].to_i
17
+ score = Time.now.to_i + ttl
18
+ client.zadd(__cache_namespace__, score, __cache_key__)
19
+ client.set(__cache_key__, Marshal.dump(self), ex: ttl)
20
+ end
21
+
22
+ def __clear_entry__!
23
+ self.class.redis_client.zrem(__cache_namespace__, __cache_key__)
24
+ self.class.redis_client.unlink(__cache_key__)
25
+ end
26
+
27
+ def __cache_key__
28
+ "#{__cache_namespace__}:#{self.class.cache_configuration[:key].call(self, @__incoming_cache_key__)}"
29
+ end
30
+
31
+ def __cache_namespace__
32
+ self.class.cache_namespace
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schematize/cache/instance_methods"
4
+ require "json_schematize/cache/class_methods"
5
+
6
+ module JsonSchematize
7
+ module Cache
8
+ def self.included(base)
9
+ raise StandardError, "Yikes! JsonSchematize::Cache Needs Redis to work. Include it as a gem" unless defined?(Redis)
10
+
11
+ base.include(JsonSchematize::Cache::InstanceMethods)
12
+ base.extend(JsonSchematize::Cache::ClassMethods)
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JsonSchematize::EmptyValue
3
+ require "json_schematize/generator"
4
+
5
+ class JsonSchematize::EmptyValue < ::JsonSchematize::Generator
4
6
  def initialize(*)
7
+ super
5
8
  end
6
9
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json_schematize/empty_value"
3
+ require "json_schematize/cache"
4
4
  require "json_schematize/field"
5
5
  require "json_schematize/introspect"
6
6
 
@@ -59,6 +59,12 @@ class JsonSchematize::Generator
59
59
  @optional_fields ||= []
60
60
  end
61
61
 
62
+ # def self.update_block(&block)
63
+ # @skip_individual_updates = true
64
+ # yield(cloned)
65
+ # @skip_individual_updates = false
66
+ # end
67
+
62
68
  def self.convenience_methods(field:)
63
69
  unless self.instance_methods.include?(:"#{field.name}=")
64
70
  define_method(:"#{field.name}=") do |value|
@@ -71,6 +77,7 @@ class JsonSchematize::Generator
71
77
  return false unless validate_value(**validate_params)
72
78
 
73
79
  instance_variable_set(:"@#{field.name}", value)
80
+ __update_cache__!
74
81
  return true
75
82
  end
76
83
  end
@@ -100,6 +107,13 @@ class JsonSchematize::Generator
100
107
 
101
108
  private
102
109
 
110
+ def __update_cache__!
111
+ return unless self.class.include?(JsonSchematize::Cache)
112
+ return unless self.class.cache_configuration[:update_on_change]
113
+
114
+ __update_cache_item__
115
+ end
116
+
103
117
  def assign_values!
104
118
  self.class.fields.each do |field|
105
119
  value = field.value_from_field(@__params)[:transformed_value]
@@ -22,6 +22,7 @@ module JsonSchematize::Introspect
22
22
  end
23
23
 
24
24
  def inspect
25
- "#<#{self.class} - required fields: #{self.class.required_fields.map(&:name)}; optional fields: #{self.class.optional_fields.map(&:name)}>"
25
+ stringify = to_h.map { |k, v| "#{k}:#{v}" }.join(", ")
26
+ "#<#{self.class} - required fields: #{self.class.required_fields.map(&:name)}; #{stringify}>"
26
27
  end
27
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsonSchematize
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require "json_schematize/base"
4
4
  require "json_schematize/boolean"
5
5
  require "json_schematize/generator"
6
+ require "json_schematize/empty_value"
6
7
  require "json_schematize/version"
7
8
 
8
9
  module JsonSchematize
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_schematize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Taylor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-13 00:00:00.000000000 Z
11
+ date: 2022-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -90,6 +90,9 @@ files:
90
90
  - lib/json_schematize.rb
91
91
  - lib/json_schematize/base.rb
92
92
  - lib/json_schematize/boolean.rb
93
+ - lib/json_schematize/cache.rb
94
+ - lib/json_schematize/cache/class_methods.rb
95
+ - lib/json_schematize/cache/instance_methods.rb
93
96
  - lib/json_schematize/empty_value.rb
94
97
  - lib/json_schematize/field.rb
95
98
  - lib/json_schematize/field_transformations.rb