json_schematize 0.5.2 → 0.6.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: 848f3c00ddb189bb1e82dc00336d599962a0f3b883318de678a14b88a3b09eb0
4
- data.tar.gz: 647b9efcb2b29a9392103397f6902b2d4bd24320392f1fadff4f5856469ddb14
3
+ metadata.gz: 7db5a295c8de919a0eb55d2894094d8c5c97f932b0955495c31ed762e84133f8
4
+ data.tar.gz: a8dc99a3affdf8027d9ec4bde5e689e84b18d1571ba126e54cb806d484ad8fb2
5
5
  SHA512:
6
- metadata.gz: b44776ceb76244451c8121c1c3a306789b4d917f71176e813a0bc477c35424dd0d6414a006ff3d17ca15efb42af412028a336303ee9dc08519c1570447118960
7
- data.tar.gz: 82db9b87e549ef31f658dce84e589372811269ab8249abe9e3913a8b6c73834c2c39fe6da1674b397e76225ea9c7283be837d15e75f24aa729ba2e0e9640e79e
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.2)
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,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json_schematize/cache"
3
4
  require "json_schematize/field"
4
5
  require "json_schematize/introspect"
5
6
 
@@ -58,6 +59,12 @@ class JsonSchematize::Generator
58
59
  @optional_fields ||= []
59
60
  end
60
61
 
62
+ # def self.update_block(&block)
63
+ # @skip_individual_updates = true
64
+ # yield(cloned)
65
+ # @skip_individual_updates = false
66
+ # end
67
+
61
68
  def self.convenience_methods(field:)
62
69
  unless self.instance_methods.include?(:"#{field.name}=")
63
70
  define_method(:"#{field.name}=") do |value|
@@ -70,6 +77,7 @@ class JsonSchematize::Generator
70
77
  return false unless validate_value(**validate_params)
71
78
 
72
79
  instance_variable_set(:"@#{field.name}", value)
80
+ __update_cache__!
73
81
  return true
74
82
  end
75
83
  end
@@ -99,6 +107,13 @@ class JsonSchematize::Generator
99
107
 
100
108
  private
101
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
+
102
117
  def assign_values!
103
118
  self.class.fields.each do |field|
104
119
  value = field.value_from_field(@__params)[:transformed_value]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsonSchematize
4
- VERSION = "0.5.2"
4
+ VERSION = "0.6.0"
5
5
  end
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.2
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-14 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