volt-repo_cache 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +314 -0
- data/Rakefile +1 -0
- data/lib/volt/repo_cache.rb +6 -0
- data/lib/volt/repo_cache/association.rb +100 -0
- data/lib/volt/repo_cache/cache.rb +116 -0
- data/lib/volt/repo_cache/collection.rb +259 -0
- data/lib/volt/repo_cache/model.rb +671 -0
- data/lib/volt/repo_cache/model_array.rb +169 -0
- data/lib/volt/repo_cache/util.rb +78 -0
- data/lib/volt/repo_cache/version.rb +5 -0
- data/spec/dummy/.gitignore +9 -0
- data/spec/dummy/README.md +4 -0
- data/spec/dummy/app/main/assets/css/app.css.scss +1 -0
- data/spec/dummy/app/main/config/dependencies.rb +11 -0
- data/spec/dummy/app/main/config/initializers/boot.rb +10 -0
- data/spec/dummy/app/main/config/routes.rb +14 -0
- data/spec/dummy/app/main/controllers/main_controller.rb +27 -0
- data/spec/dummy/app/main/models/customer.rb +4 -0
- data/spec/dummy/app/main/models/order.rb +6 -0
- data/spec/dummy/app/main/models/product.rb +5 -0
- data/spec/dummy/app/main/models/user.rb +12 -0
- data/spec/dummy/app/main/views/main/about.html +7 -0
- data/spec/dummy/app/main/views/main/index.html +6 -0
- data/spec/dummy/app/main/views/main/main.html +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/app.rb +147 -0
- data/spec/dummy/config/base/index.html +15 -0
- data/spec/dummy/config/initializers/boot.rb +4 -0
- data/spec/integration/sample_integration_spec.rb +11 -0
- data/spec/sample_spec.rb +7 -0
- data/spec/spec_helper.rb +18 -0
- data/volt-repo_cache.gemspec +38 -0
- metadata +287 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'volt/reactive/reactive_array'
|
2
|
+
|
3
|
+
module Volt
|
4
|
+
module RepoCache
|
5
|
+
class ModelArray
|
6
|
+
include Volt::RepoCache::Util
|
7
|
+
|
8
|
+
def initialize(observer: nil, contents: nil)
|
9
|
+
@contents = Volt::ReactiveArray.new(contents || [])
|
10
|
+
@id_hash = {}
|
11
|
+
@contents.each do |e|
|
12
|
+
@id_hash[e.id] = e
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# subclasses may override if interested.
|
17
|
+
def observe(action, model)
|
18
|
+
# no op
|
19
|
+
end
|
20
|
+
|
21
|
+
def index(*args, &block)
|
22
|
+
@contents.index(*args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_a
|
26
|
+
# not sure what reactive array does
|
27
|
+
# so map contents into normal array
|
28
|
+
@contents.map{|e|e}
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
@contents.size
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@contents.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def detect(*args, &block)
|
40
|
+
@contents.detect(*args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&block)
|
44
|
+
@contents.each(&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_with_index(&block)
|
48
|
+
@contents.each_with_index(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def first
|
52
|
+
@contents.first
|
53
|
+
end
|
54
|
+
|
55
|
+
def last
|
56
|
+
@contents.last
|
57
|
+
end
|
58
|
+
|
59
|
+
def count(&block)
|
60
|
+
@contents.count(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def sort(&block)
|
64
|
+
@contents.sort(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def select(&block)
|
68
|
+
@contents.select(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def reject(&block)
|
72
|
+
@contents.reject(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def collect(&block)
|
76
|
+
@contents.collect(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :map, :collect
|
80
|
+
|
81
|
+
def reduce(seed, &block)
|
82
|
+
@contents.reduce(seed, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :inject, :reduce
|
86
|
+
|
87
|
+
# Query is simple for now:
|
88
|
+
# - a hash of keys and values to match by equality
|
89
|
+
# - or a select block
|
90
|
+
# TODO: would prefer a splat to the hash,
|
91
|
+
# but Opal fails to parse calls with **splats
|
92
|
+
def query(args = nil, &block)
|
93
|
+
if args.nil? || args.empty?
|
94
|
+
if block
|
95
|
+
select &block
|
96
|
+
else
|
97
|
+
raise ArgumentError, 'query requires splat of key-value pairs, or a select block'
|
98
|
+
end
|
99
|
+
elsif args.size == 1
|
100
|
+
k, v = args.first
|
101
|
+
if k == :id
|
102
|
+
[@id_hash[v]]
|
103
|
+
else
|
104
|
+
select {|e| e.send(k) == v}
|
105
|
+
end
|
106
|
+
else
|
107
|
+
query do |e|
|
108
|
+
match = true
|
109
|
+
args.each do |k, v|
|
110
|
+
unless e.send(k) == v
|
111
|
+
match = false
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
match
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
alias_method :where, :query
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def __delete_at__(index, notify: true)
|
125
|
+
model = @contents.delete_at(index)
|
126
|
+
if model
|
127
|
+
@id_hash.delete(model.id)
|
128
|
+
observe(:remove, model) if notify
|
129
|
+
end
|
130
|
+
model
|
131
|
+
end
|
132
|
+
|
133
|
+
def __delete__(model, notify: true)
|
134
|
+
i = find_index(model)
|
135
|
+
__delete_at__(i, notify: notify) if i
|
136
|
+
end
|
137
|
+
|
138
|
+
def __clear__
|
139
|
+
@contents.clear
|
140
|
+
@id_hash.clear
|
141
|
+
end
|
142
|
+
|
143
|
+
def __remove_if_present__
|
144
|
+
__remove__(model, error_if_absent: false)
|
145
|
+
end
|
146
|
+
|
147
|
+
def __remove__(model, error_if_absent: true)
|
148
|
+
index = index {|e| e.id == model.id }
|
149
|
+
if index
|
150
|
+
result = __delete_at__(index)
|
151
|
+
# debug __method__, __LINE__, "deleted #{result.class.name} #{result.id}"
|
152
|
+
result
|
153
|
+
elsif error_if_absent
|
154
|
+
msg = "could not find #{model.class.name} with id #{model.id} to delete"
|
155
|
+
# debug __method__, __LINE__, msg
|
156
|
+
raise RuntimeError, msg
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def __append__(model, notify: true)
|
161
|
+
@contents.append(model)
|
162
|
+
@id_hash[model.id] = model
|
163
|
+
observe(:add, model) if notify
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Volt
|
2
|
+
module RepoCache
|
3
|
+
module Util
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def setter(getter)
|
7
|
+
:"#{getter}="
|
8
|
+
end
|
9
|
+
|
10
|
+
def creator(getter)
|
11
|
+
prefix_method(getter, 'new')
|
12
|
+
end
|
13
|
+
|
14
|
+
def adder(getter)
|
15
|
+
prefix_method(getter, 'add')
|
16
|
+
end
|
17
|
+
|
18
|
+
def remover(getter)
|
19
|
+
prefix_method(getter, 'remove')
|
20
|
+
end
|
21
|
+
|
22
|
+
def prefix_method(getter, prefix)
|
23
|
+
:"#{prefix}_#{getter.to_s.singularize}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def arrify(object)
|
27
|
+
object.respond_to?(:to_a) ? object.to_a : [object]
|
28
|
+
end
|
29
|
+
|
30
|
+
def unsupported(method)
|
31
|
+
fail "#{method} is an unsupported operation for #{self.class.name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def not_yet_implemented(method)
|
35
|
+
fail "#{method} is not yet implemented for #{self.class.name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def subclass_responsibility(method)
|
39
|
+
fail "#{method} is responsibility of #{self.class.name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def friends_only(__method__, caller)
|
43
|
+
unless friend?(caller)
|
44
|
+
fail "#{self.class.name}##{__method__} for Volt::RepoCache use only, not #{caller.class.name}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def friend?(object)
|
49
|
+
if object
|
50
|
+
if object.is_a?(Volt::Model)
|
51
|
+
object.respond_to?(:patched_for_cache?)
|
52
|
+
else
|
53
|
+
(object.class.name =~ /^Volt::RepoCache/) == 0
|
54
|
+
end
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def debug(method, line, msg = nil)
|
61
|
+
s = ">>> #{self.class.name}##{method}[#{line}] : #{msg}"
|
62
|
+
if RUBY_PLATFORM == 'opal'
|
63
|
+
Volt.logger.debug s
|
64
|
+
else
|
65
|
+
puts s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def time(method, line, msg = nil)
|
70
|
+
t1 = Time.now
|
71
|
+
r = yield
|
72
|
+
t2 = Time.now
|
73
|
+
debug(method, line, "#{msg} : took #{t2 - t1} seconds")
|
74
|
+
r
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
// Place your apps css here
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Specify which components you wish to include when
|
2
|
+
# the "home" component loads.
|
3
|
+
|
4
|
+
# bootstrap css framework
|
5
|
+
component 'bootstrap'
|
6
|
+
|
7
|
+
# a default theme for the bootstrap framework
|
8
|
+
component 'bootstrap_jumbotron_theme'
|
9
|
+
|
10
|
+
# provides templates for login, signup, and logout
|
11
|
+
component 'user_templates'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Place any code you want to run when the component is included on the client
|
2
|
+
# or server.
|
3
|
+
|
4
|
+
# To include code only on the client use:
|
5
|
+
# if RUBY_PLATFORM == 'opal'
|
6
|
+
#
|
7
|
+
# To include code only on the server, use:
|
8
|
+
# unless RUBY_PLATFORM == 'opal'
|
9
|
+
# ^^ this will not send compile in code in the conditional to the client.
|
10
|
+
# ^^ this include code required in the conditional.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# See https://github.com/voltrb/volt#routes for more info on routes
|
2
|
+
|
3
|
+
client '/about', action: 'about'
|
4
|
+
|
5
|
+
# Routes for login and signup, provided by user_templates component gem
|
6
|
+
client '/signup', component: 'user_templates', controller: 'signup'
|
7
|
+
client '/login', component: 'user_templates', controller: 'login', action: 'index'
|
8
|
+
client '/password_reset', component: 'user_templates', controller: 'password_reset', action: 'index'
|
9
|
+
client '/forgot', component: 'user_templates', controller: 'login', action: 'forgot'
|
10
|
+
client '/account', component: 'user_templates', controller: 'account', action: 'index'
|
11
|
+
|
12
|
+
# The main route, this should be last. It will match any params not
|
13
|
+
# previously matched.
|
14
|
+
client '/', {}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# By default Volt generates this controller for your Main component
|
2
|
+
module Main
|
3
|
+
class MainController < Volt::ModelController
|
4
|
+
def index
|
5
|
+
# Add code for when the index view is loaded
|
6
|
+
end
|
7
|
+
|
8
|
+
def about
|
9
|
+
# Add code for when the about view is loaded
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# The main template contains a #template binding that shows another
|
15
|
+
# template. This is the path to that template. It may change based
|
16
|
+
# on the params._component, params._controller, and params._action values.
|
17
|
+
def main_path
|
18
|
+
"#{params._component || 'main'}/#{params._controller || 'main'}/#{params._action || 'index'}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Determine if the current nav component is the active one by looking
|
22
|
+
# at the first part of the url against the href attribute.
|
23
|
+
def active_tab?
|
24
|
+
url.path.split('/')[1] == attrs.href.split('/')[1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# By default Volt generates this User model which inherits from Volt::User,
|
2
|
+
# you can rename this if you want.
|
3
|
+
class User < Volt::User
|
4
|
+
# login_field is set to :email by default and can be changed to :username
|
5
|
+
# in config/app.rb
|
6
|
+
field login_field
|
7
|
+
field :name
|
8
|
+
|
9
|
+
validate login_field, unique: true, length: 8
|
10
|
+
validate :email, email: true
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<:Title>
|
2
|
+
{{ view main_path, "title", {controller_group: 'main'} }}
|
3
|
+
|
4
|
+
<:Body>
|
5
|
+
<div class="container">
|
6
|
+
<div class="header">
|
7
|
+
<ul class="nav nav-pills pull-right">
|
8
|
+
<:nav href="/">Home</:nav>
|
9
|
+
<:nav href="/about">About</:nav>
|
10
|
+
<:user_templates:menu />
|
11
|
+
</ul>
|
12
|
+
<h3 class="text-muted">dummy</h3>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<:volt:notices />
|
16
|
+
|
17
|
+
{{ view main_path, 'body', {controller_group: 'main'} }}
|
18
|
+
|
19
|
+
<div class="footer">
|
20
|
+
<p>© Company {{ Time.now.year }}</p>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<:Nav>
|
26
|
+
<li class="{{ if active_tab? }}active{{ end }}">
|
27
|
+
<a href="{{ attrs.href }}">{{ yield }}</a>
|
28
|
+
</li>
|
29
|
+
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# app.rb is used to configure your app. This code is only run on the server,
|
2
|
+
# then any config options in config.public are passed to the client as well.
|
3
|
+
|
4
|
+
Volt.configure do |config|
|
5
|
+
# Setup your global app config here.
|
6
|
+
|
7
|
+
#######################################
|
8
|
+
# Basic App Info (stuff you should set)
|
9
|
+
#######################################
|
10
|
+
config.domain = 'dummy.com'
|
11
|
+
config.app_name = 'Dummy'
|
12
|
+
config.mailer.from = 'Dummy <no-reply@dummy.com>'
|
13
|
+
|
14
|
+
############
|
15
|
+
# App Secret
|
16
|
+
############
|
17
|
+
# Your app secret is used for signing things like the user cookie so it can't
|
18
|
+
# be tampered with. A random value is generated on new projects that will work
|
19
|
+
# without the need to customize. Make sure this value doesn't leave your server.
|
20
|
+
#
|
21
|
+
# For added security we recommend moving the app secret into an environment. You can
|
22
|
+
# setup that like so:
|
23
|
+
#
|
24
|
+
# config.app_secret = ENV['APP_SECRET']
|
25
|
+
#
|
26
|
+
config.app_secret = 'BidFdMW1H8fjB7icltOfGImr5G9AHOTCqgnzfZHYnVg5wgPJUS3kdekVRszLYjVmJxw'
|
27
|
+
|
28
|
+
###############
|
29
|
+
# Log Filtering
|
30
|
+
###############
|
31
|
+
# Data updates from the client come in via Tasks. The task dispatcher logs all calls to tasks.
|
32
|
+
# By default hashes in the arguments can be filtered based on keys. So any hash with a key of
|
33
|
+
# password will be filtered. You can add more fields to filter below:
|
34
|
+
config.filter_keys = [:password]
|
35
|
+
|
36
|
+
##########
|
37
|
+
# Database
|
38
|
+
##########
|
39
|
+
# Database config all start with db_ and can be set either in the config
|
40
|
+
# file or with an environment variable (DB_NAME for example).
|
41
|
+
|
42
|
+
config.db_driver = 'mongo'
|
43
|
+
config.db_name = (config.app_name + '_' + Volt.env.to_s)
|
44
|
+
config.db_host = 'localhost'
|
45
|
+
config.db_port = 27017
|
46
|
+
|
47
|
+
#####################
|
48
|
+
# Compression options
|
49
|
+
#####################
|
50
|
+
# If you are not running behind something like nginx in production, you can
|
51
|
+
# have rack deflate all files.
|
52
|
+
# config.deflate = true
|
53
|
+
|
54
|
+
#########################
|
55
|
+
# Websocket configuration
|
56
|
+
#########################
|
57
|
+
# If you need to use a different domain or path for the websocket connection,
|
58
|
+
# you can set it here. Volt provides the socket connection url at /socket,
|
59
|
+
# but if for example you are using a proxy server that doesn't support
|
60
|
+
# websockets, you can point the websocket connection at the app server
|
61
|
+
# directly.
|
62
|
+
# config.public.websocket_url = '/socket'
|
63
|
+
|
64
|
+
#######################
|
65
|
+
# Public configurations
|
66
|
+
#######################
|
67
|
+
# Anything under config.public will be sent to the client as well as the server,
|
68
|
+
# so be sure no private data ends up under public
|
69
|
+
|
70
|
+
# Use username instead of email as the login
|
71
|
+
# config.public.auth.use_username = true
|
72
|
+
|
73
|
+
#####################
|
74
|
+
# Compression Options
|
75
|
+
#####################
|
76
|
+
# Disable or enable css/js/image compression. Default is to only run in production.
|
77
|
+
# if Volt.env.production?
|
78
|
+
# config.compress_javascript = true
|
79
|
+
# config.compress_css = true
|
80
|
+
# config.compress_images = true
|
81
|
+
# end
|
82
|
+
|
83
|
+
################
|
84
|
+
# Mailer options
|
85
|
+
################
|
86
|
+
# The volt-mailer gem uses pony (https://github.com/benprew/pony) to deliver e-mail. Any
|
87
|
+
# options you would pass to pony can be setup below.
|
88
|
+
# NOTE: The from address is setup at the top
|
89
|
+
|
90
|
+
# Normally pony uses /usr/sbin/sendmail if one is installed. You can specify smtp below:
|
91
|
+
# config.mailer.via = :smtp
|
92
|
+
# config.mailer.via_options = {
|
93
|
+
# :address => 'smtp.yourserver.com',
|
94
|
+
# :port => '25',
|
95
|
+
# :user_name => 'user',
|
96
|
+
# :password => 'password',
|
97
|
+
# :authentication => :plain, # :plain, :login, :cram_md5, no auth by default
|
98
|
+
# :domain => "localhost.localdomain" # the HELO domain provided by the client to the server
|
99
|
+
# }
|
100
|
+
|
101
|
+
#############
|
102
|
+
# Message Bus
|
103
|
+
#############
|
104
|
+
# Volt provides a "Message Bus" out of the box. The message bus provides
|
105
|
+
# a pub/sub service between any volt instance (server, client, runner, etc..)
|
106
|
+
# that share the same database. The message bus can be used by app code. It
|
107
|
+
# is also used internally to push data to any listening clients.
|
108
|
+
#
|
109
|
+
# The default message bus (called "peer_to_peer") uses the database to sync
|
110
|
+
# socket ip's/ports.
|
111
|
+
# config.message_bus.bus_name = 'peer_to_peer'
|
112
|
+
#
|
113
|
+
# Encrypt message bus - messages on the message bus are encrypted by default
|
114
|
+
# using rbnacl.
|
115
|
+
|
116
|
+
#
|
117
|
+
# For dummy apps, we disable_encryption, to simplify the gem requirements.
|
118
|
+
config.message_bus.disable_encryption = true
|
119
|
+
|
120
|
+
#
|
121
|
+
# ## MessageBus Server -- the message bus binds to a port and ip which the
|
122
|
+
# other volt instances need to be able to connect to. You can customize
|
123
|
+
# the server below:
|
124
|
+
#
|
125
|
+
# Port range - you can specify a range of ports that an instance can bind the
|
126
|
+
# message bus on. You can specify a range, an array of Integers, or an array
|
127
|
+
# of ranges.
|
128
|
+
# config.message_bus.bind_port_ranges = (58000..61000)
|
129
|
+
#
|
130
|
+
# Bind Ip - specifies the ip address the message bus server should bind on.
|
131
|
+
# config.message_bus.bind_ip = '127.0.0.1'
|
132
|
+
|
133
|
+
#############
|
134
|
+
# Concurrency
|
135
|
+
#############
|
136
|
+
# Volt provides a thread worker pool for incoming task requests (and all
|
137
|
+
# database requests, since those use tasks to do their work.) The following
|
138
|
+
# lets you control the size of the worker pool. Threads are only created as
|
139
|
+
# needed, and are removed after a certain amount of inactivity.
|
140
|
+
# config.min_worker_threads = 1
|
141
|
+
# config.max_worker_threads = 10
|
142
|
+
#
|
143
|
+
# You can also specify the amount of time a Task should run for before it
|
144
|
+
# timeout's. Setting this to short can cause unexpected results, currently
|
145
|
+
# we recomend it be at least 10 seconds.
|
146
|
+
# config.worker_timeout = 60
|
147
|
+
end
|