restafarian 0.0.1.pre
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +21 -0
- data/lib/restafarian.rb +32 -0
- data/lib/restafarian/controllers/restafarian/controller.rb +7 -0
- data/lib/restafarian/controllers/restafarian/root_controller.rb +9 -0
- data/lib/restafarian/engine.rb +5 -0
- data/lib/restafarian/javascript_renderer.rb +86 -0
- data/lib/restafarian/request.rb +19 -0
- data/lib/restafarian/responder.rb +15 -0
- data/lib/restafarian/templates/representation.js.erb +41 -0
- data/lib/restafarian/templates/validators.yml +67 -0
- data/lib/restafarian/type_hinter.rb +56 -0
- data/lib/restafarian/version.rb +3 -0
- data/lib/tasks/restafarian_tasks.rake +4 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e073edea878d46f79bdb347c2443e56d53bd67ba
|
4
|
+
data.tar.gz: 4f82325b2663a332dfdb9061cd3bf09551d70e6e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3c6b392986527f19a4491db37dbf78765f5166668f18816f963d58eb6b9569a5b686348a5de0017fe2864c86a422614aa67bb7cae4a986947c9be2a379468db9
|
7
|
+
data.tar.gz: 02da1529fd207b4d36627fc72da1edf30e56aa4aaccd33771db712bf43d41187b3931ac1931dad95b3ee748dd1f86b930330c1488be21fb764c9cc7a30659007
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Restafarian'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
data/lib/restafarian.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'uncle'
|
2
|
+
require 'restafarian/engine'
|
3
|
+
require 'restafarian/request'
|
4
|
+
require 'restafarian/type_hinter'
|
5
|
+
require 'restafarian/responder'
|
6
|
+
require 'restafarian/javascript_renderer'
|
7
|
+
|
8
|
+
module Restafarian
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
ActionDispatch::Routing::Mapper.class_eval do
|
13
|
+
def restafarian_routes
|
14
|
+
root 'restafarian/root#index'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Mime::Type.register \
|
19
|
+
'application/vnd.restafarian+json', :restafarian_json
|
20
|
+
|
21
|
+
Mime::Type.register \
|
22
|
+
'application/vnd.restafarian+js', :restafarian_js
|
23
|
+
|
24
|
+
ActionController::Renderers.add(:restafarian_json) do |object, options|
|
25
|
+
self.content_type ||= Mime::RESTAFARIAN_JSON
|
26
|
+
object.kind_of?(String) ? object : object.to_json(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
ActionController::Renderers.add(:restafarian_js) do |object, options|
|
30
|
+
self.content_type ||= Mime::RESTAFARIAN_JS
|
31
|
+
Restafarian::JavascriptRenderer.new(object).render
|
32
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Restafarian
|
2
|
+
class JavascriptRenderer < Struct.new(:object)
|
3
|
+
TEMPLATE_PATH = Engine.root + 'lib/restafarian/templates'
|
4
|
+
JS_TEMPLATE = File.read(TEMPLATE_PATH + 'representation.js.erb')
|
5
|
+
VALIDATORS = YAML.load(File.read(TEMPLATE_PATH + 'validators.yml'))
|
6
|
+
ERROR_MAPPING = {
|
7
|
+
presence: [:blank], absence: [:present], length: %i<too_short too_long>,
|
8
|
+
acceptance: [:accepted], inclusion: [:inclusion],
|
9
|
+
confirmation: [:confirmation], numericality: %i<not_a_number not_an_integer>,
|
10
|
+
exclusion: [:exclusion], format:[:invalid]
|
11
|
+
}
|
12
|
+
|
13
|
+
def render
|
14
|
+
ERB.new(JS_TEMPLATE, $SAFE, '>').result(binding).each_line.map(&:strip).join
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def typed_properties
|
20
|
+
ActiveSupport::JSON.encode(object_typed_properties)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validators
|
24
|
+
VALIDATORS.slice(*pertinent_validators).
|
25
|
+
map { |k,v| "#{k}:#{(v % errors_for(k))}" }.join ","
|
26
|
+
end
|
27
|
+
|
28
|
+
def errors_for(key)
|
29
|
+
ERROR_MAPPING[key].map { |k| I18n.t('errors.messages')[k] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def pertinent_validators
|
33
|
+
names = object_as_json.keys.inject([]) do |memo, key|
|
34
|
+
validators = object.class.validators_on(key).reject do |validator|
|
35
|
+
validator.options[:on] == (object.new_record? ? :create : :update)
|
36
|
+
end
|
37
|
+
|
38
|
+
memo.push *validators.map(&:kind)
|
39
|
+
end
|
40
|
+
|
41
|
+
names.uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def type_hinter
|
45
|
+
@type_hinter ||= TypeHinter.new(object.class)
|
46
|
+
end
|
47
|
+
|
48
|
+
def object_validators_on(property)
|
49
|
+
object.class.validators_on(property).reduce({}) do |memo, validator|
|
50
|
+
options = process_options(validator.options)
|
51
|
+
memo.merge validator.kind => options
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def object_as_json
|
56
|
+
object.as_json
|
57
|
+
end
|
58
|
+
|
59
|
+
def object_name
|
60
|
+
object.class.name.humanize
|
61
|
+
end
|
62
|
+
|
63
|
+
def object_typed_properties
|
64
|
+
object_as_json.reduce({}) do |memo, (property, value)|
|
65
|
+
memo.merge property => {
|
66
|
+
label: property.humanize,
|
67
|
+
type: type_hinter.hint(property),
|
68
|
+
validators: object_validators_on(property)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_options(options)
|
74
|
+
opts = options.map do |k,v|
|
75
|
+
case v
|
76
|
+
when Regexp
|
77
|
+
v = v.source
|
78
|
+
end
|
79
|
+
|
80
|
+
[k, v]
|
81
|
+
end
|
82
|
+
|
83
|
+
Hash[opts]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Restafarian
|
2
|
+
class Request < Struct.new(:request) # :nodoc:
|
3
|
+
def acceptable_http_methods
|
4
|
+
node = routeset.routes.match(request.path).detect do |node|
|
5
|
+
node.value.any? do |r|
|
6
|
+
r.path.to_regexp === request.path && r.matches?(request)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
node.value.map { |route| route.verb.source.gsub(/[^\w]/, '') }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def routeset
|
16
|
+
Rails.application.routes
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Restafarian
|
2
|
+
class Responder < ActionController::Responder
|
3
|
+
def to_restafarian_js
|
4
|
+
request = Restafarian::Request.new(controller.request)
|
5
|
+
controller.response.headers['Allow'] = request.acceptable_http_methods.join(', ')
|
6
|
+
|
7
|
+
controller.render restafarian_js: resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_restafarian_json
|
11
|
+
controller.render restafarian_json: resource
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
(function() {
|
2
|
+
"use strict";
|
3
|
+
var validators = { <%= validators %> };
|
4
|
+
var fmt = function(error) {
|
5
|
+
return error[0].replace(/%{(\w+)}/, function(match, capture) {
|
6
|
+
return error[1][capture];
|
7
|
+
});
|
8
|
+
};
|
9
|
+
var resource = {
|
10
|
+
label: "<%= object_name %>",
|
11
|
+
properties: <%= typed_properties %>,
|
12
|
+
validate: function(representation) {
|
13
|
+
var errors = {};
|
14
|
+
|
15
|
+
for(var property in this.properties) {
|
16
|
+
errors[property] = [];
|
17
|
+
|
18
|
+
for(var validator in this.properties[property].validators) {
|
19
|
+
if(!validators[validator]) continue;
|
20
|
+
|
21
|
+
var options = this.properties[property].validators[validator];
|
22
|
+
var error = validators[validator].call(representation, property, options);
|
23
|
+
|
24
|
+
var defaults = {
|
25
|
+
attribute: property,
|
26
|
+
value: representation[property],
|
27
|
+
model: this.label
|
28
|
+
};
|
29
|
+
|
30
|
+
if(error) {
|
31
|
+
for(var key in defaults) error[1][key] = defaults[key];
|
32
|
+
errors[property].push(fmt(error));
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
return errors;
|
38
|
+
}
|
39
|
+
};
|
40
|
+
return resource;
|
41
|
+
})();
|
@@ -0,0 +1,67 @@
|
|
1
|
+
:presence: |
|
2
|
+
function(property, options) {
|
3
|
+
if(!this[property]) {
|
4
|
+
return ["%s", {}];
|
5
|
+
}
|
6
|
+
}
|
7
|
+
:absence: |
|
8
|
+
function(property, options) {
|
9
|
+
if(this[property]) {
|
10
|
+
return ["%s", {}];
|
11
|
+
}
|
12
|
+
}
|
13
|
+
:length: |
|
14
|
+
function(property, options) {
|
15
|
+
property = this[property];
|
16
|
+
|
17
|
+
if(options.minimum && property.length < options.minimum) {
|
18
|
+
return ["%s", { count: options.minimum }];
|
19
|
+
}
|
20
|
+
|
21
|
+
if(options.maximum && property.length > options.maximum) {
|
22
|
+
return ["%s", { count: options.maximum }];
|
23
|
+
}
|
24
|
+
}
|
25
|
+
:acceptance: |
|
26
|
+
function(property, options) {
|
27
|
+
if(!this[property]) {
|
28
|
+
return ["%s", {}];
|
29
|
+
}
|
30
|
+
}
|
31
|
+
:inclusion: |
|
32
|
+
function(property, options) {
|
33
|
+
if(options['in'].indexOf(this[property]) < 0) {
|
34
|
+
return ["%s", {}];
|
35
|
+
}
|
36
|
+
}
|
37
|
+
:confirmation: |
|
38
|
+
function(property, options) {
|
39
|
+
if(this[property] != this[property + '_confirmation']) {
|
40
|
+
return ["%s", {}];
|
41
|
+
}
|
42
|
+
}
|
43
|
+
:numericality: |
|
44
|
+
function(property, options) {
|
45
|
+
var number = parseInt(this[property], 10);
|
46
|
+
if(!number) {
|
47
|
+
return ["%s", {}];
|
48
|
+
}
|
49
|
+
|
50
|
+
if(options.only_integer && (String(number) != this[property])) {
|
51
|
+
return ["%s", {}];
|
52
|
+
}
|
53
|
+
}
|
54
|
+
:exclusion: |
|
55
|
+
function(property, options) {
|
56
|
+
if(options['in'].indexOf(this[property]) > -1) {
|
57
|
+
return ["%s", {}];
|
58
|
+
}
|
59
|
+
}
|
60
|
+
:format: |
|
61
|
+
function(property, options) {
|
62
|
+
var re = new RegExp(options.with);
|
63
|
+
|
64
|
+
if(!this[property].match(re)) {
|
65
|
+
return ["%s", {}];
|
66
|
+
}
|
67
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Restafarian
|
2
|
+
class TypeHinter < Struct.new(:klass)
|
3
|
+
def hint(property)
|
4
|
+
infer_from_column(property) ||
|
5
|
+
infer_from_validators(property) ||
|
6
|
+
infer_from_property_name(property)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def infer_from_property_name property
|
12
|
+
case property.to_s.gsub('_', ' ')
|
13
|
+
when /\bemail\b/ then :email
|
14
|
+
when /\bphoto\b/, /\bimage\b/, /\bavatar\b/, /\bpicture\b/ then :image
|
15
|
+
when /\bfile\b/ then :file
|
16
|
+
when /\bpassword\b/ then :password
|
17
|
+
when /\btelephone\b/, /\bphone\b/ then :tel
|
18
|
+
when /\burl\b/ then :url
|
19
|
+
when /\bnumber\b/ then :number
|
20
|
+
else :text
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def infer_from_column(property)
|
25
|
+
case mapping[property]
|
26
|
+
when :decimal, :float, :integer then :number
|
27
|
+
when :datetime then :datetime
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def infer_from_validators(property)
|
32
|
+
validators = klass.validators_on(property)
|
33
|
+
inclusion_validator = validators.detect { |v| v.kind == :inclusion }
|
34
|
+
|
35
|
+
case
|
36
|
+
when validators.any? { |v| v.kind == :acceptance }
|
37
|
+
:checkbox
|
38
|
+
when inclusion_validator.present?
|
39
|
+
infer_from_inclusion_validator \
|
40
|
+
inclusion_validator
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def infer_from_inclusion_validator(validator)
|
45
|
+
type = validator.options[:in].map do |i|
|
46
|
+
[i.humanize, i]
|
47
|
+
end
|
48
|
+
|
49
|
+
Hash[type]
|
50
|
+
end
|
51
|
+
|
52
|
+
def mapping
|
53
|
+
Hash[klass.column_types.map { |k,v| [k, v.type] }].with_indifferent_access
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restafarian
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stevie Graham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-test
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.6'
|
55
|
+
description: Expose fully RESTful HTTP APIs including code-on-demand so client can
|
56
|
+
intelligent present most appropriate UI element for each property & perform arbitrary,
|
57
|
+
non-authorative, client-side validations before submitting data to the API server.
|
58
|
+
email:
|
59
|
+
- sjtgraham@mac.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- MIT-LICENSE
|
65
|
+
- Rakefile
|
66
|
+
- lib/restafarian.rb
|
67
|
+
- lib/restafarian/controllers/restafarian/controller.rb
|
68
|
+
- lib/restafarian/controllers/restafarian/root_controller.rb
|
69
|
+
- lib/restafarian/engine.rb
|
70
|
+
- lib/restafarian/javascript_renderer.rb
|
71
|
+
- lib/restafarian/request.rb
|
72
|
+
- lib/restafarian/responder.rb
|
73
|
+
- lib/restafarian/templates/representation.js.erb
|
74
|
+
- lib/restafarian/templates/validators.yml
|
75
|
+
- lib/restafarian/type_hinter.rb
|
76
|
+
- lib/restafarian/version.rb
|
77
|
+
- lib/tasks/restafarian_tasks.rake
|
78
|
+
homepage: https://github.com/stevegraham/restafarian
|
79
|
+
licenses: []
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 1.3.1
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.2.0.rc.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: A tool for implementing real REST HTTP APIs.
|
101
|
+
test_files: []
|