wabur 0.1.0d2 → 0.2.0d1

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
  SHA1:
3
- metadata.gz: f4ae2c00f65bf680b3432bd10ec6135aeed7e6bc
4
- data.tar.gz: 01f5f822d40665b8f314d3744ccdcf7bf35c071c
3
+ metadata.gz: 86b81a950a3090451b79d8d4841b4acaa340f208
4
+ data.tar.gz: 2ac14502f2dabdd7f294be9eeae7c16f068ffadb
5
5
  SHA512:
6
- metadata.gz: 48320600e036669b67ba89b0b241440891af68ca7f59c287e632a96e3df1512cc1b917c0bb8425d9d4e7040d4c145ccb5ed28d0d3f62b228aceecc40e58f3ad2
7
- data.tar.gz: 7f83da64ecc5ed24f2bc2e225204f757873fda7f2843634bae74afb7be3dcf497098aa3d2754aa2be7f8ade8535b3096e25b3a7c9429723ead367cc1ed42bcd9
6
+ metadata.gz: 16411e53df75d17339ecdf1efe6b6afc84945c01a71ce2ff2f34a83a3a8e965ba73177286afaf273543711cc5a4646b9742c166699e2f01e80cb813cfa20a32f
7
+ data.tar.gz: 8f9ca7584c9395e39ccabaab4e0229b0f4e306651adaea727efdf4460dac1ed45e21df33073b54cdd09a3992c4ba35a0f6adf10fe496ce5a82b85cf9c9bf703e
data/README.md CHANGED
@@ -1,31 +1,34 @@
1
1
  # WABuR (Web Application Builder using Ruby)
2
2
 
3
- [![Build Status](https://img.shields.io/travis/ohler55/wabur/develop.svg)](http://travis-ci.org/ohler55/wabur?branch=develop) ![Gem](https://img.shields.io/gem/v/wabur.svg) ![Gem](https://img.shields.io/gem/dt/wabur.svg)
3
+ [![Build Status](https://img.shields.io/travis/ohler55/wabur/develop.svg)](http://travis-ci.org/ohler55/wabur?branch=develop)
4
+ [![Windows Build status](https://img.shields.io/appveyor/ci/ohler55/wabur/develop.svg?label=Windows%20build)](https://ci.appveyor.com/project/ohler55/wabur/branch/develop)
5
+ [![Gem Version](https://badge.fury.io/rb/wabur.svg)](https://rubygems.org/gems/wabur)
6
+ [![Gem](https://img.shields.io/gem/dt/wabur.svg)](https://rubygems.org/gems/wabur)
4
7
 
5
- Ruby is a great language but for performance C is a better alternative. It is
8
+ Ruby is a great language but for performance C is the better alternative. It is
6
9
  possible to get the best of both as evident with [Oj](http://www.ohler.com/oj)
7
10
  and [Ox](http://www.ohler.com/ox). C by itself allowed
8
- [Piper](http://piperpushcache.com), a fast push web server to be developed and
11
+ [Piper](http://piperpushcache.com), a fast push web server to be developed, and
9
12
  is being used to develop [OpO](http://opo.technology) a high performance graph
10
- and JSON database. This project takes from all of those projects for a hight
13
+ and JSON database. This project takes from all of those projects for a high
11
14
  performance Ruby web framework.
12
15
 
13
- Ruby on Rails has made Ruby main stream. While RoR is fine for some
16
+ Ruby on Rails has made Ruby mainstream. While RoR is fine for some
14
17
  applications there are others that might be better served with an alternative.
15
18
  This project was started as an alternative to Ruby on Rails with a focus on
16
- performance and easy of use.
19
+ performance and ease of use.
17
20
 
18
- Why develop an alternative to Rails? Rails popularity has been waning. It is
19
- still huge but not as popular as it used to be. RoR is not going away any time
20
- soon but for some applications alternatives are needed.
21
+ Why develop an alternative to Rails? The popularity of Rails has been waning.
22
+ It is still huge but not as popular as it used to be. RoR is not going away
23
+ any time soon but for some applications, alternatives are needed.
21
24
 
22
25
  ## Goals
23
26
 
24
- Lets start with the assumption that we want to continue to use Ruby. The goal
25
- of this project is to provide a high performance, easy to use, and fully
27
+ Lets start with the assumption that we want to continue using Ruby. The goal
28
+ of this project is to provide a high performance, easy to use, and a fully
26
29
  featured web framework with Ruby at the core. By keeping the core, the
27
- business logic in Ruby but allowing options for other parts to be in different
28
- languages the best use of each can be utilized.
30
+ business logic, in Ruby but allowing options for other parts to be in different
31
+ languages, the best of each can be utilized.
29
32
 
30
33
  Targets are a throughput of 100K page fetches per second at a latency of no
31
34
  more than 1 millisecond on a desktop machine. That is more than an order of
@@ -47,13 +50,13 @@ C shell that handles HTTP and data storage.
47
50
 
48
51
  ## Participate and Contribute
49
52
 
50
- If you like the idea and want to help out or become a core developer on the
51
- project send me an [email](mailto:peter@ohler.com). Get in on the ground floor
53
+ If you like the idea and want to help out, or become a core developer on the
54
+ project, send me an [email](mailto:peter@ohler.com). Get in on the ground floor
52
55
  and lets make something awesome together.
53
56
 
54
57
  ### Guidelines
55
58
 
56
- These are the simple guidelines for contrinuting.
59
+ These are the simple guidelines for contributing.
57
60
 
58
61
  1. Coordinate with me first before getting started to avoid duplication of
59
62
  effort or implementing something in conflict with the plans.
@@ -7,29 +7,22 @@ module WAB
7
7
  #
8
8
  # A description of the available methods is included as private methods.
9
9
  class Controller # :doc: all
10
- attribute_accessor :shell
11
- attribute_accessor :view
12
- attribute_accessor :model
10
+ attr_accessor :shell
13
11
 
14
12
  # Create a instance.
15
- def initialize()
16
- @shell = nil
17
- @view = shell.view
18
- @model = shell.model
19
- # TBD
13
+ def initialize(shell, async=false)
14
+ @shell = shell
15
+ # TBD handle async
20
16
  end
21
17
 
22
- # Handler for paths that do not match the REST pattern. Only called on the
23
- # default controller.
18
+ # Handler for paths that do not match the REST pattern or for unregistered
19
+ # types. Only called on the default controller.
24
20
  #
25
21
  # Processing result are passed back to the view which forward the result
26
- # on to the requester. The result, is not nil, should be a Data instance.
22
+ # on to the requester. The result, if not nil, should be a Data instance.
27
23
  #
28
- # path:: identifies operation and additional data in a / delimited
29
- # format. It is up to the controller to decide how to process the
30
- # request.
31
24
  # data:: data to be processed
32
- def handle(path, data)
25
+ def handle(data)
33
26
  nil
34
27
  end
35
28
 
@@ -38,63 +31,89 @@ module WAB
38
31
  # they will not be called.
39
32
  private
40
33
 
41
- # Should create a new data object.
34
+ # Create a new data object. If a query is provided it is treated as a
35
+ # check against an existing object with the same key/value pairs.
42
36
  #
43
- # The return should be the identifier for the object created or if
44
- # +with_data+ is true a Data object with an +id+ attribute and a +data+
45
- # attribute that contains the full object details.
37
+ # The reference to the object created is returned on success.
46
38
  #
47
39
  # On error an Exception should be raised.
48
40
  #
41
+ # path:: array of tokens in the path.
42
+ # query:: query parameters from a URL.
49
43
  # data:: the data to use as a new object.
50
- # with_data:: flag indicating the response should include the new object
51
- def create(data, with_data=false) # :doc:
52
- # TBD implement the default behavior as an example or starting point
53
- end
54
-
55
- # Should return the object with the +id+ provided.
56
- #
57
- # The function should return the result of a fetch on the model with the
58
- # provided +id+. The result should be a Data instance which is either the
59
- # object data or a wrapper that include the object id in an +id+ attribute
60
- # and the object data itself in a +data+ attribute depending on the value
61
- # of the +with_id+ argument.
62
- #
63
- # id:: identifier of the object
64
- # with_id:: if true wrap the object data with an envelope that includes
65
- # the id as well as the object data.
66
- def read(id, with_id=false) # :doc:
67
- # TBD implement the default behavior as an example or starting point
44
+ def create(path, query, data) # :doc:
45
+ tql = { }
46
+ kind = path[@shell.path_pos]
47
+ if query.is_a?(Hash) && 0 < query.size
48
+ where = ['AND']
49
+ where << form_where_eq(@shell.type_key, kind)
50
+ query.each_pair { |k,v| where << form_where_eq(k, v) }
51
+ tql[:where] = where
52
+ end
53
+ tql[:insert] = data.native
54
+ shell_query(tql, kind, 'create')
68
55
  end
69
56
 
70
- # Should return the objects with attributes matching the +attrs+ argument.
71
- #
72
- # The return should be a Hash where the keys are the matching object
73
- # identifiers and the value are the object data. An empty Hash or nil
74
- # indicates there were no matches.
75
- #
76
- # attrs:: a Hash with keys matching paths into the target objects and value
77
- # equal to the target attribute values. A path can be an array of
78
- # keys used to walk a path to the target or a +.+ delimited set of
79
- # keys.
80
- def read_by_attrs(attrs) # :doc:
81
- # TBD implement the default behavior as an example or starting point
57
+ # Return the objects according to the path and query arguments.
58
+ #
59
+ # If the path includes an object reference then that object is returned as
60
+ # the only member of the results list of a WAB::Data returned.
61
+ #
62
+ # If there is no object reference in the path then the attributes are used
63
+ # to find matching objects. The objects that have the same key/value pairs
64
+ # are returned.
65
+ #
66
+ # path:: array of tokens in the path.
67
+ # query:: query parameters from a URL as a Hash with keys matching paths
68
+ # into the target objects and value equal to the target attribute
69
+ # values. A path can be an array of keys used to walk a path to
70
+ # the target or a +.+ delimited set of keys.
71
+ def read(path, query) # :doc:
72
+ if @shell.path_pos + 2 == path.length # has an object reference in the path
73
+ ref = path[@shell.path_pos + 1].to_i
74
+ obj = @shell.get(ref)
75
+ obj = obj.native if obj.is_a?(::WAB::Data)
76
+ return @shell.data({ code: 0, results: [ { id: ref, data: obj } ]})
77
+ end
78
+ tql = { }
79
+ kind = path[@shell.path_pos]
80
+ # No id so must be either a simple query by attribute or a list.
81
+ if query.is_a?(Hash) && 0 < query.size
82
+ where = ['AND']
83
+ where << form_where_eq(@shell.type_key, kind)
84
+ query.each_pair { |k,v| where << form_where_eq(k, v) }
85
+ else
86
+ where = form_where_eq(@shell.type_key, kind)
87
+ end
88
+ tql[:where] = where
89
+ tql[:select] = { id: '$ref', data: '$' }
90
+ shell_query(tql, kind, 'read')
82
91
  end
83
92
 
84
93
  # Replaces the object data for the identified object.
85
94
  #
86
- # The return should be the identifier for the object updated or if
87
- # +with_data+ is true a Data object with an +id+ attribute and a +data+
88
- # attribute that contains the full object details. Note that depending on
89
- # the implemenation the identifier may change as a result of an update.
95
+ # The return should be the identifiers for the object updated.
90
96
  #
91
97
  # On error an Exception should be raised.
92
98
  #
93
- # id:: identifier of the object to be replaced
99
+ # path:: array of tokens in the path.
100
+ # query:: query parameters from a URL.
94
101
  # data:: the data to use as a new object.
95
- # with_data:: flag indicating the response should include the new object
96
- def update(id, data, with_data=false) # :doc:
97
- # TBD implement the default behavior as an example or starting point
102
+ def update(path, query, data) # :doc:
103
+ tql = { }
104
+ kind = path[@shell.path_pos]
105
+ if @shell.path_pos + 2 == path.length # has an object reference in the path
106
+ tql[:where] = path[@shell.path_pos + 1].to_i
107
+ elsif query.is_a?(Hash) && 0 < query.size
108
+ where = ['AND']
109
+ where << form_where_eq(@shell.type_key, kind)
110
+ query.each_pair { |k,v| where << form_where_eq(k, v) }
111
+ tql[:where] = where
112
+ else
113
+ raise ::WAB::Error.new("update on all #{kind} not allowed.")
114
+ end
115
+ tql[:update] = data.native
116
+ shell_query(tql, kind, 'update')
98
117
  end
99
118
 
100
119
  # Delete the identified object.
@@ -102,54 +121,30 @@ module WAB
102
121
  # On success the deleted object identifier is returned. If the object is
103
122
  # not found then nil is returned. On error an Exception should be raised.
104
123
  #
105
- # id:: identifier of the object to be deleted
106
- def delete(id) # :doc:
107
- # TBD implement the default behavior as an example or starting point
108
- end
109
-
110
- # Delete all object that match the set of provided attribute values.
111
- #
112
- # An array of deleted object identifiers should be returned.
113
- #
114
- # attrs:: a Hash with keys matching paths into the target objects and value
115
- # equal to the target attribute values. A path can be an array of
116
- # keys used to walk a path to the target or a +.+ delimited set of
117
- # keys.
118
- def delete_by_attrs(attrs) # :doc:
119
- # TBD implement the default behavior as an example or starting point
120
- end
121
-
122
- # Return a Hash of all the objects of the type associated with the
123
- # controller.
124
- #
125
- # The return hash keys should be the identifiers of the objects and the
126
- # the values should be either nil or the object data if the +with_data+
127
- # flag is true. If the response will be larger than supported one of the
128
- # keys should be the empty string which indicated additional instance
129
- # exists and were not provided.
130
- #
131
- # Note that this could return a very large set of data. If the number of
132
- # instances in the type is large the +search()+ might be more appropriate
133
- # as it allows for paging of results and sorting.
134
- #
135
- # with_data:: flag indicating the return should include object data
136
- def list(with_data=false) # :doc:
137
- # TBD implement the default behavior as an example or starting point
138
- end
139
-
140
- # Search using a TQL SELECT.
141
- #
142
- # The provided TQL[http://opo.technology/pages/doc/tql/index.html] can be
143
- # either the JSON syntax or the friendly syntax. The call exists on the
144
- # controller to allow filtering and permission checking before
145
- # execution. Only the default controller is expected to provide a public
146
- # version of this method.
147
- #
148
- # query:: query
149
- # format:: can be one of :TQL or :TQL_JSON. The :GraphQL option is
150
- # reserved for the future.
151
- def search(query, format=:TQL) # :doc:
152
- # TBD implement the default behavior as an example or starting point
124
+ # If no +id+ is present in the path then the return should be a Hash where
125
+ # the keys are the matching object identifiers and the value are the
126
+ # object data. An empty Hash or nil indicates there were no matches.
127
+ #
128
+ # path:: identifier of the object to be deleted
129
+ # query:: query parameters from a URL as a Hash with keys matching paths
130
+ # into the target objects and value equal to the target attribute
131
+ # values. A path can be an array of keys used to walk a path to
132
+ # the target or a +.+ delimited set of keys.
133
+ def delete(path, query) # :doc:
134
+ tql = { }
135
+ kind = path[@shell.path_pos]
136
+ if @shell.path_pos + 2 == path.length # has an object reference in the path
137
+ tql[:where] = path[@shell.path_pos + 1].to_i
138
+ elsif query.is_a?(Hash) && 0 < query.size
139
+ where = ['AND']
140
+ where << form_where_eq(@shell.type_key, kind)
141
+ query.each_pair { |k,v| where << form_where_eq(k, v) }
142
+ tql[:where] = where
143
+ else
144
+ tql[:where] = form_where_eq(@shell.type_key, kind)
145
+ end
146
+ tql[:delete] = nil
147
+ shell_query(tql, kind, 'delete')
153
148
  end
154
149
 
155
150
  # Subscribe to changes in data pushed from the model that will be passed
@@ -168,7 +163,61 @@ module WAB
168
163
  #
169
164
  # data:: the data that has changed
170
165
  def changed(data) # :doc:
171
- # TBD implement the default behavior as an example or starting point
166
+ # TBD filter accoding to subscriptions
167
+ @shell.changed(data)
168
+ end
169
+
170
+ # Form a EQ expression for a TQL where clause. Used as a helper to the
171
+ # primary API calls.
172
+ #
173
+ # key:: key in the expression
174
+ # value:: value portion converted to the correct format if necessary
175
+ def form_where_eq(key, value)
176
+ value_class = value.class
177
+ x = ['EQ', key.to_s]
178
+ if value.is_a?(String)
179
+ x << "'" + value
180
+ elsif Time == value_class
181
+ x << value.utc.iso8601(9)
182
+ elsif value.nil? ||
183
+ TrueClass == value_class ||
184
+ FalseClass == value_class ||
185
+ Integer == value_class ||
186
+ Float == value_class
187
+ x << value
188
+ elsif String == value_class
189
+ if 0 < value.length && '\'' == value[0]
190
+ x << value[1..-1]
191
+ elsif /^-?\d+$/.match?(value)
192
+ x << value.to_i
193
+ elsif /^-?\d*\.?\d+([eE][-+]?\d+)?$/.match?(value)
194
+ x << value.to_f
195
+ end
196
+ # TBD detect other types UUID, HTTP, Time
197
+
198
+ elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
199
+ x << value
200
+ else
201
+ x << value.to_s
202
+ end
203
+ x
204
+ end
205
+
206
+ # Helper to send TQL requests to the shell either synchronously or
207
+ # asynchronously depending on the controller type.
208
+ def shell_query(tql, kind, op)
209
+
210
+ # TBD check for async or not
211
+
212
+ result = @shell.query(tql, nil) # synchronous call
213
+ if result.nil? || 0 != result[:code]
214
+ if result.nil?
215
+ raise ::WAB::Error.new("nil result on #{kind} #{op}.")
216
+ else
217
+ raise ::WAB::Error.new("error on #{kind} #{op}. #{result[:error]}")
218
+ end
219
+ end
220
+ result
172
221
  end
173
222
 
174
223
  end # Controller
data/lib/wab/data.rb CHANGED
@@ -85,6 +85,14 @@ module WAB
85
85
  raise NotImplementedError.new
86
86
  end
87
87
 
88
+ # Detects and converts strings to Ruby objects following the rules:
89
+ # Time:: "2017-01-05T15:04:33.123456789Z", zulu only
90
+ # UUID:: "b0ca922d-372e-41f4-8fea-47d880188ba3"
91
+ # URI:: "http://opo.technology/sample", HTTP only
92
+ def detect()
93
+ raise NotImplementedError.new
94
+ end
95
+
88
96
  private
89
97
 
90
98
  # This method is included only to raise an error if an attempt is made to
data/lib/wab/errors.rb ADDED
@@ -0,0 +1,13 @@
1
+
2
+ module WAB
3
+
4
+ # Base for WAB errors and exceptions.
5
+ class Error < StandardError
6
+ end # Error
7
+
8
+ # An Exception that is raised as a result of a parse error while parsing a
9
+ # JSON document.
10
+ class ParseError < Error
11
+ end # ParseError
12
+
13
+ end # Oj
data/lib/wab/impl/data.rb CHANGED
@@ -21,10 +21,10 @@ module WAB
21
21
  #
22
22
  # value:: initial value
23
23
  # repair:: flag indicating invalid value should be repaired if possible
24
- def initialize(value, repair)
24
+ def initialize(value, repair, check=true)
25
25
  if repair
26
26
  value = fix(value)
27
- else
27
+ elsif check
28
28
  validate(value)
29
29
  end
30
30
  @root = value
@@ -35,23 +35,28 @@ module WAB
35
35
  # array of path node identifiers. For example, child.grandchild is the
36
36
  # same as ['child', 'grandchild'].
37
37
  def get(path)
38
- path = path.to_s.split('.') unless path.is_a?(Array)
39
- node = @root
40
- path.each { |key|
41
- if node.is_a?(Hash)
42
- node = node[key.to_sym]
43
- elsif node.is_a?(Array)
44
- i = key.to_i
45
- if 0 == i && '0' != key && 0 != key
38
+ if path.is_a?(Symbol)
39
+ node = @root[path]
40
+ else
41
+ path = path.to_s.split('.') unless path.is_a?(Array)
42
+ node = @root
43
+ path.each { |key|
44
+ if node.is_a?(Hash)
45
+ node = node[key.to_sym]
46
+ elsif node.is_a?(Array)
47
+ i = key.to_i
48
+ if 0 == i && '0' != key && 0 != key
49
+ node = nil
50
+ break
51
+ end
52
+ node = node[i]
53
+ else
46
54
  node = nil
47
55
  break
48
56
  end
49
- node = node[i]
50
- else
51
- node = nil
52
- break
53
- end
54
- }
57
+ }
58
+ end
59
+ return Data.new(node, false, false) if node.is_a?(Hash) || node.is_a?(Array)
55
60
  node
56
61
  end
57
62
 
@@ -70,7 +75,9 @@ module WAB
70
75
  # repair:: flag indicating invalid value should be repaired if possible
71
76
  def set(path, value, repair=false)
72
77
  raise StandardError.new("path can not be empty.") if path.empty?
73
- if repair
78
+ if value.is_a?(::WAB::Data)
79
+ value = value.native
80
+ elsif repair
74
81
  value = fix_value(value)
75
82
  else
76
83
  validate_value(value)
@@ -177,6 +184,14 @@ module WAB
177
184
  Oj.dump(@root, mode: :wab, indent: indent)
178
185
  end
179
186
 
187
+ # Detects and converts strings to Ruby objects following the rules:
188
+ # Time:: "2017-01-05T15:04:33.123456789Z", zulu only
189
+ # UUID:: "b0ca922d-372e-41f4-8fea-47d880188ba3"
190
+ # URI:: "http://opo.technology/sample", HTTP only
191
+ def detect()
192
+ # TBD
193
+ end
194
+
180
195
  private
181
196
 
182
197
  # Raise an exception if the value is not a suitable data element. If the
@@ -223,6 +238,8 @@ module WAB
223
238
  value.each { |v|
224
239
  validate_value(v)
225
240
  }
241
+ elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
242
+ # valid value
226
243
  else
227
244
  raise StandardError.new("#{value_class.to_s} is not a valid Data value.")
228
245
  end
@@ -281,6 +298,8 @@ module WAB
281
298
  old.each { |v|
282
299
  value << fix_value(v)
283
300
  }
301
+ elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
302
+ # valid value
284
303
  elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
285
304
  value = value.to_h
286
305
  raise StandardError.new("Data values must be either a Hash or an Array") unless value.is_a?(Hash)
@@ -361,7 +380,19 @@ module WAB
361
380
  c = []
362
381
  value.each { |v| c << clone_value(v) }
363
382
  else
364
- c = value.clone
383
+ value_class = value.class
384
+ if value.nil? ||
385
+ TrueClass == value_class ||
386
+ FalseClass == value_class ||
387
+ Integer == value_class ||
388
+ Float == value_class ||
389
+ String == value_class
390
+ c = value
391
+ elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
392
+ c = value
393
+ else
394
+ c = value.clone
395
+ end
365
396
  end
366
397
  c
367
398
  end
@@ -9,7 +9,7 @@ module WAB
9
9
  class Shell < ::WAB::Shell
10
10
 
11
11
  # Sets up the shell with a view, model, and type_key.
12
- def initialize(view, model, type_key='kind')
12
+ def initialize(type_key='kind', path_pos=0)
13
13
  super
14
14
  end
15
15
 
@@ -0,0 +1,23 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+
6
+ module IO
7
+
8
+ class Call
9
+
10
+ attr_accessor :rid
11
+ attr_accessor :result
12
+ attr_accessor :thread
13
+
14
+ def initialize(timeout=2.0)
15
+ @rid = nil
16
+ @result = nil
17
+ @thread = Thread.current
18
+ @giveup = Time.now + timeout
19
+ end
20
+
21
+ end # Call
22
+ end # IO
23
+ end # WAB