delorean_lang 0.4.6 → 0.4.7

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
- 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