rack-scaffold 0.0.1

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: 62eaf4d70bb8f5ffe5b174b33713b1ebeae39bc5
4
+ data.tar.gz: 60452a102b8b505f53dfdc1a6ef2b32a56931ccf
5
+ SHA512:
6
+ metadata.gz: 5f9b7caa071e3813f3b0115e68db9ada8c116f57d5283594d3c6811a84faed2781a745ea1866dea66d722d3d9121058cd6f4f78c16c8dcf5009c3990fd51604d
7
+ data.tar.gz: 36a22a42bbd975c11cfec13ca44f239774061b8a8a7115eeda608bf96031b84ec74813a7eddc113d72cbac3d1ec125cb1f1e43ee2171c4c578dae97ac351bc09
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-scaffold (0.0.1)
5
+ activesupport (>= 3.0)
6
+ rack (~> 1.4)
7
+ rack-contrib (~> 1.1)
8
+ sinatra (~> 1.3)
9
+ sinatra-param (~> 0.1)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ activesupport (3.2.13)
15
+ i18n (= 0.6.1)
16
+ multi_json (~> 1.0)
17
+ i18n (0.6.1)
18
+ multi_json (1.7.2)
19
+ rack (1.5.2)
20
+ rack-contrib (1.1.0)
21
+ rack (>= 0.9.1)
22
+ rack-protection (1.5.0)
23
+ rack
24
+ rake (10.0.4)
25
+ sinatra (1.4.2)
26
+ rack (~> 1.5, >= 1.5.2)
27
+ rack-protection (~> 1.4)
28
+ tilt (~> 1.3, >= 1.3.4)
29
+ sinatra-param (0.1.2)
30
+ sinatra (~> 1.3)
31
+ tilt (1.3.7)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ rack-scaffold!
38
+ rake
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Mattt Thompson (http://mattt.me/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # Rack::Scaffold
2
+ **Automatically generate RESTful CRUD services**
3
+
4
+ > This project generalizes the webservice auto-generation functionality of [Rack::CoreData](https://github.com/mattt/rack-core-data) with a plugin architecture that can adapt to any data model format. It is currently being actively developed for inclusion in the next release of [Helios](https://github.com/helios-framework/helios)
5
+
6
+ ### Supported Data Models
7
+
8
+ - [Core Data Model](https://github.com/mattt/core_data/) (`.xcdatamodeld`)
9
+ - [Sequel](https://github.com/jeremyevans/sequel)
10
+ - [ActiveRecord](https://github.com/rails/rails)
11
+
12
+ ## Usage
13
+
14
+ ### Gemfile
15
+
16
+ ```Ruby
17
+ source :rubygems
18
+
19
+ gem 'rack-scaffold', require: 'rack/scaffold'
20
+ gem 'sequel'
21
+ gem 'core_data'
22
+
23
+ gem 'unicorn'
24
+ gem 'pg'
25
+ ```
26
+
27
+ ### config.ru
28
+
29
+ ```ruby
30
+ require 'sequel'
31
+ require 'core_data'
32
+ require 'rack/scaffold'
33
+
34
+ DB = Sequel.connect(ENV['DATABASE_URL'])
35
+
36
+ run Rack::Scaffold model: './Example.xcdatamodeld', only: [:create, :read]
37
+ ```
38
+
39
+ ## Examples
40
+
41
+ An example web API using a Core Data model can be found the `/example` directory.
42
+
43
+ ## Contact
44
+
45
+ Mattt Thompson
46
+
47
+ - http://github.com/mattt
48
+ - http://twitter.com/mattt
49
+ - m@mattt.me
50
+
51
+ ## License
52
+
53
+ Rack::Scaffold is available under the MIT license. See the LICENSE file for more info.
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("rack-scaffold.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["rack-scaffold.gemspec"] do
9
+ system "gem build rack-scaffold.gemspec"
10
+ end
@@ -0,0 +1,121 @@
1
+ require 'rack'
2
+ require 'rack/contrib'
3
+ require 'sinatra/base'
4
+ require 'sinatra/param'
5
+
6
+ require 'rack/scaffold/version'
7
+ require 'rack/scaffold/adapters'
8
+
9
+ module Rack
10
+ class Scaffold
11
+ ACTIONS = [:create, :read, :update, :destroy]
12
+
13
+ def initialize(options = {})
14
+ raise ArgumentError, "Missing option: :model or :models" unless options[:model] or options[:models]
15
+
16
+ if options[:models] and options[:models].kind_of?(Array)
17
+ @app = Rack::Cascade.new(options.delete(:models).collect{|model| self.class.new(options.dup.merge({model: model}))}) and return
18
+ end
19
+
20
+ @app = Class.new(Sinatra::Base) do
21
+ use Rack::PostBodyContentTypeParser
22
+ helpers Sinatra::Param
23
+
24
+ before do
25
+ content_type :json
26
+ end
27
+
28
+ disable :raise_errors, :show_exceptions
29
+ end
30
+
31
+ @actions = (options[:only] || ACTIONS) - (options[:except] || [])
32
+
33
+ @adapter = Rack::Scaffold.adapters.detect{|adapter| adapter === options[:model]}
34
+ raise "No suitable adapters found for #{options[:model]} in #{Rack::Scaffold.adapters}" unless @adapter
35
+
36
+ resources = Array(@adapter.resources(options[:model]))
37
+ resources.each do |resource|
38
+ @app.instance_eval do
39
+ post "/#{resource.plural}/?" do
40
+ if record = resource.create!(params)
41
+ status 201
42
+ {entity.name.downcase => record}.to_json
43
+ else
44
+ status 406
45
+ {errors: record.errors}.to_json
46
+ end
47
+ end
48
+ end if @actions.include?(:create)
49
+
50
+ @app.instance_eval do
51
+ get "/#{resource.plural}/?" do
52
+ if params[:page] or params[:per_page]
53
+ param :page, Integer, default: 1, min: 1
54
+ param :per_page, Integer, default: 100, in: (1..100)
55
+
56
+ {
57
+ "#{resource.plural}" => resource.paginate(params[:per_page], (params[:page] - 1) * params[:per_page]),
58
+ page: params[:page],
59
+ total: resource.count
60
+ }.to_json
61
+ else
62
+ param :limit, Integer, default: 100, in: (1..100)
63
+ param :offset, Integer, default: 0, min: 0
64
+
65
+ {
66
+ "#{resource.plural}" => resource.paginate(params[:limit], params[:offset])
67
+ }.to_json
68
+ end
69
+ end
70
+
71
+ get "/#{resource.plural}/:id/?" do
72
+ record = resource[params[:id]] or halt 404
73
+ {entity.name.downcase => record}.to_json
74
+ end
75
+ end if @actions.include?(:read)
76
+
77
+ @app.instance_eval do
78
+ put "/#{resource.plural}/:id/?" do
79
+ record = resource[params[:id]] or halt 404
80
+ if record.update!(params)
81
+ status 200
82
+ {entity.name.downcase => record}.to_json
83
+ else
84
+ status 406
85
+ {errors: record.errors}.to_json
86
+ end
87
+ end
88
+ end if @actions.include?(:update)
89
+
90
+ @app.instance_eval do
91
+ delete "/#{resource.plural}/:id/?" do
92
+ record = resource[params[:id]] or halt 404
93
+ if record.destroy
94
+ status 200
95
+ else
96
+ status 406
97
+ {errors: record.errors}.to_json
98
+ end
99
+ end
100
+ end if @actions.include?(:delete)
101
+
102
+ # @app.instance_eval do
103
+ # entity.relationships.each do |relationship|
104
+ # next unless relationship.to_many?
105
+
106
+ # get "/#{resource.plural}/:id/#{relationship.name}/?" do
107
+ # {relationship.name => resource[params[:id]].send(relationship.name)}.to_json
108
+ # end
109
+ # end
110
+ # end
111
+ end
112
+ end
113
+
114
+ def call(env)
115
+ @app.call(env)
116
+ end
117
+ end
118
+
119
+ module Models
120
+ end
121
+ end
@@ -0,0 +1,78 @@
1
+ module Rack
2
+ class Scaffold
3
+ def self.adapters
4
+ @@adapters ||= []
5
+ end
6
+
7
+ module Adapters
8
+ class NotImplementedError < StandardError; end
9
+
10
+ class Base
11
+ attr_reader :klass
12
+
13
+ class << self
14
+ def inherited(adapter)
15
+ ::Rack::Scaffold.adapters << adapter
16
+ super
17
+ end
18
+
19
+ def ===(model)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def resources(model)
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+
28
+ def initialize(klass)
29
+ @klass = klass
30
+ end
31
+
32
+ def singular
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def plural
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def count
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def all
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def paginate(offset, limit)
49
+ raise NotImplementedError
50
+ end
51
+
52
+ def [](id)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ def find(options = {})
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def create!(attributes = {})
61
+ raise NotImplementedError
62
+ end
63
+
64
+ def update!(attributes = {})
65
+ raise NotImplementedError
66
+ end
67
+
68
+ def destroy!
69
+ raise NotImplementedError
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ require 'rack/scaffold/adapters/active_record' if defined?(ActiveRecord::Base)
77
+ require 'rack/scaffold/adapters/sequel' if defined?(Sequel)
78
+ require 'rack/scaffold/adapters/core_data' if defined?(Sequel) and defined?(CoreData)
@@ -0,0 +1,36 @@
1
+ require 'active_record'
2
+ require 'forwardable'
3
+
4
+ module Rack::Scaffold::Adapters
5
+ class ActiveRecord < Base
6
+ extend Forwardable
7
+
8
+ def_delegators :@klass, :count, :all, :find, :create!, :update!, :destroy!
9
+
10
+ class << self
11
+ def ===(model)
12
+ ::ActiveRecord::Base === model
13
+ end
14
+
15
+ def resources(model)
16
+ model
17
+ end
18
+ end
19
+
20
+ def singular
21
+ @klass.name.downcase
22
+ end
23
+
24
+ def plural
25
+ @klass.table_name
26
+ end
27
+
28
+ def paginate(limit, offset)
29
+ @klass.limit(limit).offset(offset)
30
+ end
31
+
32
+ def [](id)
33
+ self.find(id)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,114 @@
1
+ require 'core_data'
2
+ require 'sequel'
3
+ require 'active_support/inflector'
4
+
5
+ module Rack::Scaffold::Adapters
6
+ class CoreData < Sequel
7
+ class << self
8
+ def ===(model)
9
+ return true if ::CoreData::DataModel === model
10
+ !! ::CoreData::DataModel.new(model) rescue false
11
+ end
12
+
13
+ def resources(xcdatamodel)
14
+ model = ::CoreData::DataModel.new(xcdatamodel)
15
+ model.entities.collect{|entity| new(entity)}
16
+ end
17
+ end
18
+
19
+ def initialize(entity)
20
+ klass = Class.new(::Sequel::Model)
21
+ klass.dataset = entity.name.downcase.pluralize.to_sym
22
+
23
+ klass.class_eval do
24
+ self.strict_param_setting = false
25
+ self.raise_on_save_failure = false
26
+
27
+ plugin :json_serializer, naked: true, include: [:url]
28
+ plugin :schema
29
+ plugin :validation_helpers
30
+
31
+ def url
32
+ "/#{self.class.table_name}/#{self[primary_key]}"
33
+ end
34
+
35
+ # entity.relationships.each do |relationship|
36
+ # options = {:class => Rack::Scaffold::Models.const_get(relationship.destination.capitalize)}
37
+
38
+ # if relationship.to_many?
39
+ # one_to_many relationship.name.to_sym, options
40
+ # else
41
+ # many_to_one relationship.name.to_sym, options
42
+ # end
43
+ # end
44
+
45
+ set_schema do
46
+ primary_key :id
47
+
48
+ entity.attributes.each do |attribute|
49
+ next if attribute.transient?
50
+
51
+ options = {
52
+ :null => attribute.optional?,
53
+ :index => attribute.indexed?,
54
+ :default => attribute.default_value
55
+ }
56
+
57
+ type = case attribute.type
58
+ when "Integer 16" then :int2
59
+ when "Integer 32" then :int4
60
+ when "Integer 64" then :int8
61
+ when "Float" then :float4
62
+ when "Double" then :float8
63
+ when "Decimal" then :float8
64
+ when "Date" then :timestamp
65
+ when "Boolean" then :boolean
66
+ when "Binary" then :bytea
67
+ else :varchar
68
+ end
69
+
70
+ column attribute.name.to_sym, type, options
71
+ end
72
+
73
+ entity.relationships.each do |relationship|
74
+ options = {
75
+ :index => true,
76
+ :null => relationship.optional?
77
+ }
78
+
79
+ if not relationship.to_many?
80
+ column "#{relationship.name}_id".to_sym, :integer, options
81
+ end
82
+ end
83
+ end
84
+
85
+ if table_exists?
86
+ missing_columns = schema.columns.reject{|c| columns.include?(c[:name])}
87
+ db.alter_table table_name do
88
+ missing_columns.each do |options|
89
+ add_column options.delete(:name), options.delete(:type), options
90
+ end
91
+ end
92
+ else
93
+ create_table
94
+ end
95
+ end
96
+
97
+ klass.send :define_method, :validate do
98
+ entity.attributes.each do |attribute|
99
+ case attribute.type
100
+ when "Integer 16", "Integer 32", "Integer 64"
101
+ validates_integer attribute.name
102
+ when "Float", "Double", "Decimal"
103
+ validates_numeric attribute.name
104
+ when "String"
105
+ validates_min_length attribute.minimum_value, attribute.name if attribute.minimum_value
106
+ validates_max_length attribute.maximum_value, attribute.name if attribute.maximum_value
107
+ end
108
+ end
109
+ end
110
+
111
+ super(klass)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,32 @@
1
+ require 'sequel'
2
+ require 'forwardable'
3
+
4
+ module Rack::Scaffold::Adapters
5
+ class Sequel < Base
6
+ extend Forwardable
7
+
8
+ def_delegators :@klass, :count, :all, :find, :[], :create!, :update!, :destroy!
9
+
10
+ class << self
11
+ def ===(model)
12
+ ::Sequel::Model === model
13
+ end
14
+
15
+ def resources(model)
16
+ model
17
+ end
18
+ end
19
+
20
+ def singular
21
+ @klass.name
22
+ end
23
+
24
+ def plural
25
+ @klass.table_name
26
+ end
27
+
28
+ def paginate(limit, offset)
29
+ @klass.limit(limit, offset)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Scaffold
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/scaffold/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-scaffold"
7
+ s.authors = ["Mattt Thompson"]
8
+ s.email = "m@mattt.me"
9
+ s.homepage = "http://mattt.me"
10
+ s.license = "MIT"
11
+ s.version = Rack::Scaffold::VERSION
12
+ s.platform = Gem::Platform::RUBY
13
+ s.summary = "Rack::Scaffold"
14
+ s.description = "Automatically generate RESTful CRUD services"
15
+
16
+ s.add_dependency "rack", "~> 1.4"
17
+ s.add_dependency "rack-contrib", "~> 1.1"
18
+ s.add_dependency "sinatra", "~> 1.3"
19
+ s.add_dependency "sinatra-param", "~> 0.1"
20
+ s.add_dependency "activesupport", ">= 3.0"
21
+
22
+ s.add_development_dependency "rake"
23
+
24
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/ }
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-scaffold
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mattt Thompson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack-contrib
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra-param
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Automatically generate RESTful CRUD services
98
+ email: m@mattt.me
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ./Gemfile
104
+ - ./Gemfile.lock
105
+ - ./lib/rack/scaffold/adapters/active_record.rb
106
+ - ./lib/rack/scaffold/adapters/core_data.rb
107
+ - ./lib/rack/scaffold/adapters/sequel.rb
108
+ - ./lib/rack/scaffold/adapters.rb
109
+ - ./lib/rack/scaffold/version.rb
110
+ - ./lib/rack/scaffold.rb
111
+ - ./LICENSE
112
+ - ./rack-scaffold.gemspec
113
+ - ./Rakefile
114
+ - ./README.md
115
+ homepage: http://mattt.me
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.0.3
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Rack::Scaffold
139
+ test_files: []