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.
@@ -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
@@ -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.
@@ -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
+
@@ -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,7 @@
1
+ module Restafarian
2
+ class Controller < ActionController::Base
3
+ respond_to :restafarian_js, :restafarian_json
4
+
5
+ self.responder = Restafarian::Responder
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Restafarian
2
+ class RootController < ::ApplicationController
3
+ def index
4
+ # raise request.path.inspect
5
+ render json: { child_resources: child_resources },
6
+ content_type: 'application/vnd.restafarian+json'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Restafarian
2
+ class Engine < Rails::Engine
3
+ paths["app/controllers"] << "lib/restafarian/controllers"
4
+ end
5
+ 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
@@ -0,0 +1,3 @@
1
+ module Restafarian
2
+ VERSION = "0.0.1.pre"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :restafarian do
3
+ # # Task goes here
4
+ # 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: []