delorean_lang 0.4.6 → 0.4.7

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
- SHA256:
3
- metadata.gz: c5f384dcfb07455b430f7e5cc39832349e90102061dd4938ae1c91b87705cd01
4
- data.tar.gz: 53cc908c85b3ee1a22254aadc4e20994fd41d49b9dc1997de49faba053348b53
2
+ SHA1:
3
+ metadata.gz: f606ebe7eea8de6ef41dcc2de653b09ed4177308
4
+ data.tar.gz: b545ce6a844aa31945cd590f73a456a23c68e613
5
5
  SHA512:
6
- metadata.gz: af272886e5cfff2facfec56ec8a7b4e4b49453752740b07b45aff6ab1d2e47359707a04d7451e89bc7f58bd9752a4cdc88a859ed4299fee4de23eab96dc98774
7
- data.tar.gz: ce15844ba019fa48b70ec47ed9fbe6f22956309375dd297a0cdab0ae289d5d663d0ad037cc82cb61f59de9014c570ad322d96240723aa17b94d1b63184c50d57
6
+ metadata.gz: d934f9c0172103dc572132b42ca728749801d50a3f63e87574d4788c09c995f4a475aaa9c96ebfc6f430732190fc2dd059e285587a9b53f1a406bb65d09289d0
7
+ data.tar.gz: 87ce61ccb1bce7c5e89df4bb8d0b975adcc4297cfaa8b913c5f318a02cee2d9b658479b446e81c13b73490d26a7d1429387a9d0053cefd26f0beb3e6df19d974
data/README.md CHANGED
@@ -26,9 +26,9 @@ Or add it to your `Gemfile`, etc.
26
26
  NodeB: NodeA
27
27
  attr3 = attr1 / NodeA.attr3
28
28
  eom
29
-
29
+
30
30
  engine.parse my_code
31
-
31
+
32
32
  engine.evaluate("NodeB", %w{attr1 attr2 attr3})
33
33
 
34
34
  ## The Delorean Language
@@ -73,7 +73,7 @@ Therefore, in the above example, `NodeA.attr2` evaluates to `246`.
73
73
  Delorean attribute definitions have the following form:
74
74
 
75
75
  attr = expression
76
-
76
+
77
77
  Where `attr` is an attribute name. Attribute names are required to
78
78
  match the following regular expression: `[a-z][a-zA-Z0-9_]*`. An
79
79
  attribute can only be specified once in a node. Also, any attributes
@@ -111,7 +111,7 @@ nodes. The following example shows the usage of inheritance:
111
111
 
112
112
  IndiaInfo: USInfo
113
113
  teen_min = 10
114
-
114
+
115
115
  In this example, node `USInfo` provides a definition of a
116
116
  `is_teenager` when provided with an `age` parameter. Node `IndiaInfo`
117
117
  is derived from `USInfo` and so it shares all of its attribute
@@ -135,6 +135,51 @@ TODO: provide details on the following topics:
135
135
  This implementation of Delorean "compiles" script code to
136
136
  Ruby.
137
137
 
138
+ ### Caching
139
+
140
+ Delorean provides `cached_delorean_function` method that will cache result based on arguments.
141
+
142
+ ```ruby
143
+ cached_delorean_fn :returns_cached_openstruct, sig: 1 do |timestamp|
144
+ User.all
145
+ end
146
+
147
+ ```
148
+
149
+ If `::Delorean::Cache.adapter.cache_item?(...)` returns `false` then caching will not be performed.
150
+
151
+ By default cache keeps the last 1000 of the results per class. You can override it:
152
+
153
+ ```ruby
154
+
155
+ ::Delorean::Cache.adapter = ::Delorean::Cache::Adapters::RubyCache.new(size_per_class: 10)
156
+
157
+ ```
158
+
159
+ If you want use other caching method, you can use your own adapter:
160
+
161
+ ```ruby
162
+
163
+ ::Delorean::Cache.adapter = ::My::Custom::Cache::Adapter.new
164
+
165
+ ```
166
+
167
+ Delorean expects it to have methods with following signatures:
168
+
169
+ ```ruby
170
+
171
+ cache_item(klass:, cache_key:, item:)
172
+ fetch_item(klass:, cache_key:)
173
+ cache_key(klass:, method_name:, args:)
174
+ clear!(klass:)
175
+ clear_all!
176
+ cache_item?(klass:, method_name:, args:)
177
+
178
+ # See lib/delorean/cache/adapters/base.rb
179
+
180
+ ```
181
+
182
+
138
183
  TODO: provide details
139
184
 
140
185
  ## License
data/lib/delorean/base.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  require 'active_support/time'
2
2
  require 'active_record'
3
3
  require 'bigdecimal'
4
+ require 'delorean/cache'
4
5
 
5
6
  module Delorean
6
7
 
8
+ ::Delorean::Cache.adapter = ::Delorean::Cache::Adapters::RubyCache.new(size_per_class: 1000)
9
+
7
10
  TI_TYPES = [Time, ActiveSupport::TimeWithZone]
8
11
  DT_TYPES = [Date] + TI_TYPES
9
12
  NUM_OR_STR = [Numeric, String]
@@ -0,0 +1,33 @@
1
+ module Delorean
2
+ module Cache
3
+ module Adapters
4
+ class Base
5
+ def cache_item(klass:, cache_key:, args:)
6
+ raise 'cache_item is not implemented'
7
+ end
8
+
9
+ def fetch_item(klass:, cache_key:, args:)
10
+ raise 'fetch_item is not implemented'
11
+ end
12
+
13
+ def cache_key(klass:, method_name:, args:)
14
+ raise 'cache_key is not implemented'
15
+ end
16
+
17
+ def clear!(klass:)
18
+ raise 'clear! is not implemented'
19
+ end
20
+
21
+ def clear_all!
22
+ raise 'clear_all! is not implemented'
23
+ end
24
+
25
+ # Redefine this method in descendants
26
+ # to avoid caching calls with certain arguments
27
+ def cache_item?(klass:, method_name:, args:)
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ require_relative './base'
2
+
3
+ module Delorean
4
+ module Cache
5
+ module Adapters
6
+ class RubyCache < ::Delorean::Cache::Adapters::Base
7
+ attr_reader :lookup_cache, :size_per_class
8
+
9
+ def initialize(size_per_class: 1000)
10
+ @lookup_cache = {}
11
+ @size_per_class = size_per_class
12
+ end
13
+
14
+ def cache_item(klass:, cache_key:, item:)
15
+ lookup_cache[klass] ||= {}
16
+ clear_outdated_items(klass: klass)
17
+ lookup_cache[klass][cache_key] = item
18
+ end
19
+
20
+ def fetch_item(klass:, cache_key:)
21
+ lookup_cache.dig(klass, cache_key)
22
+ end
23
+
24
+ def cache_key(klass:, method_name:, args:)
25
+ [method_name] + args.map do |arg|
26
+ next arg.id if arg.respond_to?(:id)
27
+ arg
28
+ end.freeze
29
+ end
30
+
31
+ def clear!(klass:)
32
+ lookup_cache[klass] = {}
33
+ end
34
+
35
+ def clear_all!
36
+ @lookup_cache = {}
37
+ end
38
+
39
+ private
40
+
41
+ def clear_outdated_items(klass:)
42
+ cache_object = lookup_cache[klass]
43
+ return unless cache_object
44
+ return if cache_object.count < size_per_class
45
+
46
+ max_items = (size_per_class / 5).floor
47
+ cache_object.keys[0..max_items].each do |key|
48
+ cache_object.delete(key)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,8 @@
1
+ require 'delorean/cache/adapters/ruby_cache'
2
+
3
+ module Delorean
4
+ module Cache
5
+ module Adapters
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ require 'delorean/cache/adapters'
2
+
3
+ module Delorean
4
+ module Cache
5
+ def self.adapter
6
+ @adapter
7
+ end
8
+
9
+ def self.adapter=(new_adapter)
10
+ @adapter = new_adapter
11
+ end
12
+ end
13
+ end
@@ -22,6 +22,52 @@ module Delorean
22
22
  self.const_set(name.to_s.upcase+Delorean::SIG, sig)
23
23
  end
24
24
  end
25
+
26
+ # FIXME IDEA: we just make :cache an argument to delorean_fn.
27
+ # That way, we don't need the cached_ flavors. It'll make all
28
+ # this code a lot simpler. We should also just add the :private
29
+ # mechanism here.
30
+
31
+ # By default implements a VERY HACKY class-based (per process) caching
32
+ # mechanism for database lookup results. Issues include: cached
33
+ # values are ActiveRecord objects. Query results can be very
34
+ # large lists which we count as one item in the cache. Caching
35
+ # mechanism will result in large processes.
36
+ def cached_delorean_fn(name, options = {}, &block)
37
+ delorean_fn(name, options) do |args|
38
+ delorean_cache_adapter = ::Delorean::Cache.adapter
39
+ # Check if caching should be performed
40
+ next block.call(args) unless delorean_cache_adapter.cache_item?(
41
+ klass: self, method_name: name, args: args
42
+ )
43
+
44
+ cache_key = delorean_cache_adapter.cache_key(
45
+ klass: self, method_name: name, args: [args]
46
+ )
47
+ cached_item = delorean_cache_adapter.fetch_item(
48
+ klass: self, cache_key: cache_key
49
+ )
50
+
51
+ next cached_item if cached_item
52
+
53
+ res = block.call(args)
54
+
55
+ delorean_cache_adapter.cache_item(
56
+ klass: self, cache_key: cache_key, item: res
57
+ )
58
+
59
+ # Since we're caching this object and don't want anyone
60
+ # changing it. FIXME: ideally should freeze this object
61
+ # recursively.
62
+ res.freeze if res.respond_to?(:freeze)
63
+
64
+ res
65
+ end
66
+ end
67
+
68
+ def clear_lookup_cache!
69
+ ::Delorean::Cache.adapter.clear!(klass: self)
70
+ end
25
71
  end
26
72
  end
27
73
  end
@@ -1,3 +1,3 @@
1
1
  module Delorean
2
- VERSION = "0.4.6"
2
+ VERSION = "0.4.7"
3
3
  end
data/lib/delorean_lang.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "delorean/version"
2
2
 
3
3
  require 'treetop'
4
+ require 'delorean/cache'
4
5
  require 'delorean/delorean'
5
6
  require 'delorean/nodes'
6
7
  require 'delorean/engine'
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Delorean cache" do
4
+ before do
5
+ Dummy.clear_lookup_cache!
6
+ end
7
+
8
+ it 'allows to set adapter' do
9
+ ::Delorean::Cache.adapter = ::Delorean::Cache::Adapters::RubyCache.new(
10
+ size_per_class: 100
11
+ )
12
+ expect(::Delorean::Cache.adapter.size_per_class).to eq 100
13
+ end
14
+
15
+ it 'uses cache' do
16
+ expect(OpenStruct).to receive(:new).once.and_call_original
17
+
18
+ res1 = Dummy.returns_cached_openstruct
19
+ res2 = Dummy.returns_cached_openstruct
20
+
21
+ expect(res1).to eq res2
22
+ end
23
+
24
+ it 'clears cache' do
25
+ expect(OpenStruct).to receive(:new).twice.and_call_original
26
+ Dummy.returns_cached_openstruct
27
+ Dummy.clear_lookup_cache!
28
+ Dummy.returns_cached_openstruct
29
+ end
30
+
31
+ it "doesn't use cache with different keys" do
32
+ expect(OpenStruct).to receive(:new).twice.and_call_original
33
+
34
+ Dummy.returns_cached_openstruct(1)
35
+ Dummy.returns_cached_openstruct(2)
36
+ end
37
+
38
+ it 'removes outdated items from cache' do
39
+ ::Delorean::Cache.adapter = ::Delorean::Cache::Adapters::RubyCache.new(
40
+ size_per_class: 10
41
+ )
42
+
43
+ 12.times do |t|
44
+ Dummy.returns_cached_openstruct(t)
45
+ end
46
+
47
+ item_2 = ::Delorean::Cache.adapter.fetch_item(
48
+ klass: Dummy, cache_key: [:returns_cached_openstruct, 2]
49
+ )
50
+
51
+ item_10 = ::Delorean::Cache.adapter.fetch_item(
52
+ klass: Dummy, cache_key: [:returns_cached_openstruct, 10]
53
+ )
54
+
55
+ expect(item_2).to_not be_present
56
+ expect(item_10).to be_present
57
+ end
58
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
4
  require 'rspec'
5
5
  require 'delorean_lang'
6
6
  require 'active_record'
7
+ require 'pry'
7
8
 
8
9
  # Requires supporting files with custom matchers and macros, etc,
9
10
  # in ./support/ and its subdirectories.
@@ -86,6 +87,10 @@ class Dummy < ActiveRecord::Base
86
87
  delorean_fn :returns_openstruct, sig: 0 do
87
88
  OpenStruct.new({"abc"=>"def"})
88
89
  end
90
+
91
+ cached_delorean_fn :returns_cached_openstruct, sig: 1 do |ts = nil|
92
+ OpenStruct.new({"abc"=>"def"})
93
+ end
89
94
  end
90
95
 
91
96
  class DummyChild < Dummy
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delorean_lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-05 00:00:00.000000000 Z
11
+ date: 2019-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: treetop
@@ -96,6 +96,10 @@ files:
96
96
  - Rakefile
97
97
  - delorean.gemspec
98
98
  - lib/delorean/base.rb
99
+ - lib/delorean/cache.rb
100
+ - lib/delorean/cache/adapters.rb
101
+ - lib/delorean/cache/adapters/base.rb
102
+ - lib/delorean/cache/adapters/ruby_cache.rb
99
103
  - lib/delorean/const.rb
100
104
  - lib/delorean/container.rb
101
105
  - lib/delorean/debug.rb
@@ -107,6 +111,7 @@ files:
107
111
  - lib/delorean/nodes.rb
108
112
  - lib/delorean/version.rb
109
113
  - lib/delorean_lang.rb
114
+ - spec/cache_spec.rb
110
115
  - spec/dev_spec.rb
111
116
  - spec/eval_spec.rb
112
117
  - spec/func_spec.rb
@@ -132,11 +137,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
137
  version: '0'
133
138
  requirements: []
134
139
  rubyforge_project:
135
- rubygems_version: 2.7.7
140
+ rubygems_version: 2.6.14
136
141
  signing_key:
137
142
  specification_version: 4
138
143
  summary: Delorean compiler
139
144
  test_files:
145
+ - spec/cache_spec.rb
140
146
  - spec/dev_spec.rb
141
147
  - spec/eval_spec.rb
142
148
  - spec/func_spec.rb