restafarian 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|