ki 0.4.11 → 0.4.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +9 -0
- data/.ruby-gemset +1 -1
- data/.ruby-version +1 -1
- data/.travis.yml +10 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +95 -85
- data/Guardfile +20 -14
- data/MIDDLEWARE.md +2 -2
- data/README.md +29 -1
- data/Rakefile +3 -1
- data/bin/ki +1 -0
- data/ki.gemspec +15 -14
- data/lib/ki.rb +8 -1
- data/lib/ki/base_request.rb +7 -5
- data/lib/ki/channel_manager.rb +2 -0
- data/lib/ki/helpers.rb +4 -5
- data/lib/ki/ki.rb +11 -2
- data/lib/ki/ki_app.rb +2 -0
- data/lib/ki/ki_cli.rb +9 -2
- data/lib/ki/ki_config.rb +5 -2
- data/lib/ki/ki_logger.rb +15 -0
- data/lib/ki/middleware/admin_interface_generator.rb +3 -1
- data/lib/ki/middleware/api_handler.rb +3 -1
- data/lib/ki/middleware/base_middleware.rb +2 -0
- data/lib/ki/middleware/coffee_compiler.rb +2 -0
- data/lib/ki/middleware/haml_compiler.rb +2 -0
- data/lib/ki/middleware/helpers/format_of_helper.rb +3 -1
- data/lib/ki/middleware/helpers/haml_compiler_helper.rb +7 -5
- data/lib/ki/middleware/helpers/public_file_helper.rb +2 -0
- data/lib/ki/middleware/helpers/redirect_to_helper.rb +2 -0
- data/lib/ki/middleware/helpers/view_helper.rb +2 -0
- data/lib/ki/middleware/init_middleware.rb +2 -0
- data/lib/ki/middleware/{doc_generator.rb → insta_doc.rb} +8 -6
- data/lib/ki/middleware/public_file_server.rb +2 -0
- data/lib/ki/middleware/realtime.rb +3 -1
- data/lib/ki/middleware/sass_compiler.rb +2 -0
- data/lib/ki/model.rb +8 -7
- data/lib/ki/modules/callbacks.rb +12 -20
- data/lib/ki/modules/model_helper.rb +2 -0
- data/lib/ki/modules/query_interface.rb +2 -0
- data/lib/ki/modules/restrictions.rb +5 -2
- data/lib/ki/orm.rb +20 -13
- data/lib/ki/utils/annotations.rb +33 -0
- data/lib/ki/utils/api_error.rb +14 -4
- data/lib/ki/utils/descendants.rb +9 -0
- data/lib/ki/utils/extra_irb.rb +3 -1
- data/lib/ki/utils/extra_ruby.rb +12 -0
- data/lib/ki/utils/indifferent_hash.rb +2 -0
- data/lib/ki/utils/logger.rb +5 -0
- data/lib/ki/version.rb +3 -1
- data/lib/ki/views/instadoc.haml +49 -9
- data/spec/config.yml.example +1 -1
- data/spec/examples/base/client.rb +20 -0
- data/spec/examples/base/logs/.keep +0 -0
- data/spec/examples/base/public/javascripts/app.js +12 -0
- data/spec/examples/json.northpole.ro/.ruby-version +1 -1
- data/spec/examples/json.northpole.ro/public/javascripts/app.coffee +1 -0
- data/spec/functional_spec.rb +2 -0
- data/spec/lib/ki/base_request_spec.rb +25 -23
- data/spec/lib/ki/channel_manager_spec.rb +2 -0
- data/spec/lib/ki/helpers_spec.rb +4 -2
- data/spec/lib/ki/indifferent_hash_spec.rb +2 -0
- data/spec/lib/ki/ki_app_spec.rb +2 -0
- data/spec/lib/ki/ki_config_spec.rb +5 -3
- data/spec/lib/ki/ki_spec.rb +2 -0
- data/spec/lib/ki/middleware/admin_generator_spec.rb +2 -0
- data/spec/lib/ki/middleware/haml_compiler_spec.rb +6 -3
- data/spec/lib/ki/middleware/helpers/format_of_helper_spec.rb +4 -2
- data/spec/lib/ki/middleware/init_middleware_spec.rb +3 -1
- data/spec/lib/ki/middleware/{doc_generator_spec.rb → insta_doc_spec.rb} +3 -1
- data/spec/lib/ki/middleware/realtime_spec.rb +12 -6
- data/spec/lib/ki/middleware_spec.rb +3 -1
- data/spec/lib/ki/model_spec.rb +26 -22
- data/spec/lib/ki/modules/model_helper_spec.rb +5 -3
- data/spec/lib/ki/modules/restrictions_spec.rb +16 -0
- data/spec/lib/ki/orm_spec.rb +18 -15
- data/spec/lib/ki/utils/api_error_spec.rb +9 -0
- data/spec/spec_helper.rb +13 -7
- data/spec/util_spec.rb +3 -1
- metadata +44 -30
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ki
|
2
4
|
class Model
|
3
5
|
module Restrictions
|
@@ -28,9 +30,10 @@ module Ki
|
|
28
30
|
private
|
29
31
|
|
30
32
|
def generic_restriction(method_name, attributes)
|
31
|
-
|
33
|
+
attributes += send(method_name) if defined? method_name
|
34
|
+
%i[define_method define_singleton_method].each do |definition_means|
|
32
35
|
send definition_means, method_name do
|
33
|
-
attributes
|
36
|
+
attributes.sort.uniq
|
34
37
|
end
|
35
38
|
end
|
36
39
|
end
|
data/lib/ki/orm.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
|
3
5
|
module Ki
|
@@ -29,14 +31,16 @@ module Ki
|
|
29
31
|
# config.yml.
|
30
32
|
#
|
31
33
|
def establish_connection
|
34
|
+
Mongo::Logger.logger.level = Logger::FATAL
|
35
|
+
# Mongo::Logger.logger = Logger.new('mongo.log')
|
36
|
+
# Mongo::Logger.logger.level = Logger::INFO
|
37
|
+
|
32
38
|
@config = KiConfig.instance.database
|
33
|
-
if ENV['MONGODB_URI']
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@db = @connection.db(@config['name'])
|
39
|
-
end
|
39
|
+
@db = if ENV['MONGODB_URI']
|
40
|
+
Mongo::Client.new
|
41
|
+
else
|
42
|
+
Mongo::Client.new('mongodb://' + connection_string)
|
43
|
+
end
|
40
44
|
self
|
41
45
|
end
|
42
46
|
|
@@ -76,8 +80,9 @@ module Ki
|
|
76
80
|
# db.insert 'users', { name: 'Homer' }
|
77
81
|
#
|
78
82
|
def insert(name, hash)
|
79
|
-
@db[name].
|
80
|
-
[
|
83
|
+
item = @db[name].insert_one(hash)
|
84
|
+
hash['id'] = item.inserted_id.to_s
|
85
|
+
hash
|
81
86
|
end
|
82
87
|
|
83
88
|
# Find a hash in the database
|
@@ -137,7 +142,7 @@ module Ki
|
|
137
142
|
hash = nourish_hash_id hash
|
138
143
|
id = hash['_id'].to_s
|
139
144
|
hash.delete('_id')
|
140
|
-
@db[name].
|
145
|
+
@db[name].update_one({ '_id' => BSON::ObjectId(id) }, hash)
|
141
146
|
hash['id'] = id
|
142
147
|
hash
|
143
148
|
end
|
@@ -161,9 +166,9 @@ module Ki
|
|
161
166
|
#
|
162
167
|
def delete(name, hash)
|
163
168
|
hash = nourish_hash_id hash
|
164
|
-
r = @db[name].
|
169
|
+
r = @db[name].delete_many hash
|
165
170
|
{
|
166
|
-
deleted_item_count: r['n']
|
171
|
+
deleted_item_count: r.documents[0]['n']
|
167
172
|
}
|
168
173
|
end
|
169
174
|
|
@@ -206,6 +211,7 @@ module Ki
|
|
206
211
|
hash
|
207
212
|
end
|
208
213
|
|
214
|
+
# TODO: write tests
|
209
215
|
def nourish_hash_limit(hash)
|
210
216
|
tmp = {}
|
211
217
|
if hash['__limit']
|
@@ -217,6 +223,7 @@ module Ki
|
|
217
223
|
[hash, tmp]
|
218
224
|
end
|
219
225
|
|
226
|
+
# TODO: write tests
|
220
227
|
def nourish_hash_sort(hash)
|
221
228
|
tmp = {}
|
222
229
|
if hash['__sort']
|
@@ -227,7 +234,7 @@ module Ki
|
|
227
234
|
# TODO: validate value
|
228
235
|
# TODO: handle sorting by id
|
229
236
|
hash['__sort'].to_a.each do |e|
|
230
|
-
tmp[e[0].to_sym] = e[1]
|
237
|
+
tmp[e[0].to_sym] = e[1]
|
231
238
|
end
|
232
239
|
end
|
233
240
|
hash.delete('__sort')
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# https://stackoverflow.com/questions/3157426/how-to-simulate-java-like-annotations-in-ruby
|
4
|
+
module Ki
|
5
|
+
module Annotations
|
6
|
+
def annotations(meth = nil)
|
7
|
+
return @__annotations__[meth] if meth
|
8
|
+
@__annotations__
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def method_added(m)
|
14
|
+
(@__annotations__ ||= {})[m] = @__last_annotation__ if @__last_annotation__
|
15
|
+
@__last_annotation__ = nil
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(meth, *args)
|
20
|
+
return super unless /\A_/.match?(meth)
|
21
|
+
@__last_annotation__ ||= {}
|
22
|
+
@__last_annotation__[meth[1..-1].to_sym] = args.size == 1 ? args.first : args
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Module
|
28
|
+
private
|
29
|
+
|
30
|
+
def annotate!
|
31
|
+
extend Ki::Annotations
|
32
|
+
end
|
33
|
+
end
|
data/lib/ki/utils/api_error.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Ki
|
2
4
|
class ApiError < StandardError #:nodoc:
|
5
|
+
extend Descendants
|
6
|
+
|
3
7
|
attr_reader :status
|
4
8
|
|
5
|
-
def initialize(body, status = 400)
|
6
|
-
super body
|
9
|
+
def initialize(body = nil, status = 400)
|
10
|
+
super body.nil? ? to_s : body
|
7
11
|
@status = status
|
8
12
|
end
|
9
13
|
|
10
14
|
def result
|
11
|
-
{
|
15
|
+
{
|
16
|
+
'error' => to_s,
|
17
|
+
'status' => @status
|
18
|
+
}
|
12
19
|
end
|
13
20
|
end
|
14
21
|
|
@@ -34,8 +41,11 @@ module Ki
|
|
34
41
|
end
|
35
42
|
|
36
43
|
class PartialNotFoundError < ApiError #:nodoc:
|
37
|
-
def initialize(s)
|
44
|
+
def initialize(s = '"partial"')
|
38
45
|
super "partial #{s} not found", 404
|
39
46
|
end
|
40
47
|
end
|
41
48
|
end
|
49
|
+
|
50
|
+
class CustomError < Ki::ApiError
|
51
|
+
end
|
data/lib/ki/utils/extra_irb.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# http://jasonroelofs.com/2009/04/02/embedding-irb-into-your-ruby-application/
|
2
4
|
require 'irb'
|
3
5
|
|
@@ -15,7 +17,7 @@ module IRB # :nodoc:
|
|
15
17
|
|
16
18
|
irb = Irb.new(workspace)
|
17
19
|
|
18
|
-
@CONF[:IRB_RC]
|
20
|
+
@CONF[:IRB_RC]&.call(irb.context)
|
19
21
|
@CONF[:MAIN_CONTEXT] = irb.context
|
20
22
|
|
21
23
|
catch(:IRB_EXIT) do
|
data/lib/ki/utils/extra_ruby.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class String
|
2
4
|
# Converts a string to a class
|
3
5
|
#
|
@@ -45,3 +47,13 @@ class NilClass
|
|
45
47
|
false
|
46
48
|
end
|
47
49
|
end
|
50
|
+
|
51
|
+
class Object
|
52
|
+
def try(*a, &b)
|
53
|
+
if a.empty? && block_given?
|
54
|
+
yield self
|
55
|
+
else
|
56
|
+
__send__(*a, &b)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/ki/version.rb
CHANGED
data/lib/ki/views/instadoc.haml
CHANGED
@@ -5,12 +5,52 @@
|
|
5
5
|
%li
|
6
6
|
%div
|
7
7
|
%h2= model.to_s
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
%
|
12
|
-
|
13
|
-
= model.
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
- unless model.annotations.nil?
|
9
|
+
- unless model.annotations[:doc].nil?
|
10
|
+
%p= model.annotations[:doc][:desc]
|
11
|
+
%sub
|
12
|
+
Endpoint:
|
13
|
+
= "/#{model.to_s.downcase}.json"
|
14
|
+
-# TODO: if any attributes
|
15
|
+
%h3 Attributes
|
16
|
+
- [ { items: model.required_attributes, key: :required_attributes }, { items: model.forbidden_actions, key: :forbidden_actions }, { items: model.unique_attributes, key: :unique_attributes }].each do |array|
|
17
|
+
- if array[:items].any?
|
18
|
+
%p
|
19
|
+
%b= array[:key]
|
20
|
+
- array[:items].each do |item|
|
21
|
+
%p
|
22
|
+
= item
|
23
|
+
- unless model.annotations.nil?
|
24
|
+
- unless model.annotations[array[:key]].nil?
|
25
|
+
%i= model.annotations[array[:key]][item]
|
26
|
+
-# TODO: if any callbacks
|
27
|
+
%h3 Callbacks
|
28
|
+
- Ki::Model::Callbacks.public_instance_methods.each do |pim|
|
29
|
+
- unless model.annotations.nil?
|
30
|
+
- unless model.annotations[pim].nil?
|
31
|
+
%b= pim
|
32
|
+
- model.annotations[pim].keys.each do |pim_key|
|
33
|
+
%p
|
34
|
+
= pim_key
|
35
|
+
%i= model.annotations[pim][pim_key]
|
36
|
+
-# %p{style: 'color: gray'}
|
37
|
+
-# = model.annotations
|
38
|
+
%li
|
39
|
+
%div
|
40
|
+
%h2 Errors
|
41
|
+
%h3 Api Errors
|
42
|
+
%ul
|
43
|
+
%li
|
44
|
+
= Ki::ApiError
|
45
|
+
- Ki::ApiError.descendants.each do |error|
|
46
|
+
%li
|
47
|
+
= error
|
48
|
+
%h3 Custom Errors
|
49
|
+
%ul
|
50
|
+
- CustomError.descendants.each do |error|
|
51
|
+
%li
|
52
|
+
= error
|
53
|
+
|
54
|
+
%h3 Sample response
|
55
|
+
%pre
|
56
|
+
= Ki::ApiError.new('Bad request').result.to_json
|
data/spec/config.yml.example
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'faye/websocket'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
EM.run {
|
5
|
+
ws = Faye::WebSocket::Client.new('ws://localhost:1337/faye')
|
6
|
+
|
7
|
+
ws.on :open do |event|
|
8
|
+
p [:open]
|
9
|
+
ws.send('Hello, world!')
|
10
|
+
end
|
11
|
+
|
12
|
+
ws.on :message do |event|
|
13
|
+
p [:message, event.data]
|
14
|
+
end
|
15
|
+
|
16
|
+
ws.on :close do |event|
|
17
|
+
p [:close, event.code, event.reason]
|
18
|
+
ws = nil
|
19
|
+
end
|
20
|
+
}
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// var client = new Faye.Client('/faye');
|
2
|
+
// client.disable('autodisconnect');
|
3
|
+
// client.publish('/message', {text: 'Hi there'});
|
4
|
+
// var subscription = client.subscribe('/message', function(message) {
|
5
|
+
// console.log(message);
|
6
|
+
// }).then(function () {
|
7
|
+
// console.log('active');
|
8
|
+
// });
|
9
|
+
|
10
|
+
var socket = new WebSocket("ws://localhost:1337/faye");
|
11
|
+
socket.onmessage = function(m) {console.log(m);}
|
12
|
+
socket.onclose = function() {console.log('socket closed');}
|
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.4.3
|
data/spec/functional_spec.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Ki::BaseRequest do
|
4
6
|
let(:req) { Ki::BaseRequest }
|
5
7
|
|
6
8
|
it 'knows if current path is root' do
|
7
|
-
req.new({ 'PATH_INFO' => '/' }).
|
8
|
-
req.new({ 'PATH_INFO' => '/foo' }).
|
9
|
+
expect(req.new({ 'PATH_INFO' => '/' })).to be_root
|
10
|
+
expect(req.new({ 'PATH_INFO' => '/foo' })).to_not be_root
|
9
11
|
end
|
10
12
|
|
11
13
|
it 'coverts path to class' do
|
@@ -13,37 +15,37 @@ describe Ki::BaseRequest do
|
|
13
15
|
class Bar < Ki::Model; end
|
14
16
|
class FooBar < Ki::Model; end
|
15
17
|
|
16
|
-
req.new({ 'PATH_INFO' => '' }).to_ki_model_class.
|
17
|
-
req.new({ 'PATH_INFO' => '/' }).to_ki_model_class.
|
18
|
-
req.new({ 'PATH_INFO' => 'foo' }).to_ki_model_class.
|
19
|
-
req.new({ 'PATH_INFO' => '/foo' }).to_ki_model_class.
|
20
|
-
req.new({ 'PATH_INFO' => '/Foo.json' }).to_ki_model_class.
|
21
|
-
req.new({ 'PATH_INFO' => '/bar.json' }).to_ki_model_class.
|
22
|
-
req.new({ 'PATH_INFO' => '/Foo/bar.json' }).to_ki_model_class.
|
23
|
-
req.new({ 'PATH_INFO' => '/Foo/bar' }).to_ki_model_class.
|
24
|
-
req.new({ 'PATH_INFO' => '/Foo_bar' }).to_ki_model_class.
|
25
|
-
req.new({ 'PATH_INFO' => '/foo_bar' }).to_ki_model_class.
|
26
|
-
req.new({ 'PATH_INFO' => '/foo_bar.json' }).to_ki_model_class.
|
18
|
+
expect(req.new({ 'PATH_INFO' => '' }).to_ki_model_class).to be nil
|
19
|
+
expect(req.new({ 'PATH_INFO' => '/' }).to_ki_model_class).to be nil
|
20
|
+
expect(req.new({ 'PATH_INFO' => 'foo' }).to_ki_model_class).to be Foo
|
21
|
+
expect(req.new({ 'PATH_INFO' => '/foo' }).to_ki_model_class).to be Foo
|
22
|
+
expect(req.new({ 'PATH_INFO' => '/Foo.json' }).to_ki_model_class).to be Foo
|
23
|
+
expect(req.new({ 'PATH_INFO' => '/bar.json' }).to_ki_model_class).to be Bar
|
24
|
+
expect(req.new({ 'PATH_INFO' => '/Foo/bar.json' }).to_ki_model_class).to be nil
|
25
|
+
expect(req.new({ 'PATH_INFO' => '/Foo/bar' }).to_ki_model_class).to be nil
|
26
|
+
expect(req.new({ 'PATH_INFO' => '/Foo_bar' }).to_ki_model_class).to be FooBar
|
27
|
+
expect(req.new({ 'PATH_INFO' => '/foo_bar' }).to_ki_model_class).to be FooBar
|
28
|
+
expect(req.new({ 'PATH_INFO' => '/foo_bar.json' }).to_ki_model_class).to be FooBar
|
27
29
|
end
|
28
30
|
|
29
31
|
it 'converts verb to action' do
|
30
|
-
req.new({ 'REQUEST_METHOD' => 'GET' }).to_action.
|
31
|
-
req.new({ 'REQUEST_METHOD' => 'POST' }).to_action.
|
32
|
-
req.new({ 'REQUEST_METHOD' => 'PUT' }).to_action.
|
33
|
-
req.new({ 'REQUEST_METHOD' => 'DELETE' }).to_action.
|
34
|
-
req.new({ 'REQUEST_METHOD' => 'SEARCH' }).to_action.
|
32
|
+
expect(req.new({ 'REQUEST_METHOD' => 'GET' }).to_action).to be :find
|
33
|
+
expect(req.new({ 'REQUEST_METHOD' => 'POST' }).to_action).to be :create
|
34
|
+
expect(req.new({ 'REQUEST_METHOD' => 'PUT' }).to_action).to be :update
|
35
|
+
expect(req.new({ 'REQUEST_METHOD' => 'DELETE' }).to_action).to be :delete
|
36
|
+
expect(req.new({ 'REQUEST_METHOD' => 'SEARCH' }).to_action).to be :find
|
35
37
|
end
|
36
38
|
|
37
39
|
context 'json' do
|
38
40
|
it 'considers application/json content type as a json request' do
|
39
|
-
req.new({ 'CONTENT_TYPE' => 'application/xml' }).
|
40
|
-
req.new({}).
|
41
|
-
req.new({ 'CONTENT_TYPE' => 'application/json' }).
|
41
|
+
expect(req.new({ 'CONTENT_TYPE' => 'application/xml' })).to_not be_json
|
42
|
+
expect(req.new({})).to_not be_json
|
43
|
+
expect(req.new({ 'CONTENT_TYPE' => 'application/json' })).to be_json
|
42
44
|
end
|
43
45
|
|
44
46
|
it 'considers .json url format as a json request' do
|
45
|
-
req.new({ 'PATH_INFO' => '/foo' }).
|
46
|
-
req.new({ 'PATH_INFO' => '/foo.json' }).
|
47
|
+
expect(req.new({ 'PATH_INFO' => '/foo' })).to_not be_json
|
48
|
+
expect(req.new({ 'PATH_INFO' => '/foo.json' })).to be_json
|
47
49
|
end
|
48
50
|
|
49
51
|
# context 'params' do
|