sinatra-rest-api 0.1.3 → 0.1.4

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rubocop.yml +34 -0
  4. data/Gemfile +20 -0
  5. data/Gemfile.lock +92 -0
  6. data/LICENSE +5 -0
  7. data/README.md +135 -0
  8. data/examples/active_record/Gemfile +15 -0
  9. data/examples/active_record/Gemfile.lock +75 -0
  10. data/examples/active_record/Rakefile +8 -0
  11. data/examples/active_record/app.rb +117 -0
  12. data/examples/active_record/config.ru +8 -0
  13. data/examples/active_record/database.yml +7 -0
  14. data/examples/active_record/db/migrate/001_create_authors.rb +11 -0
  15. data/examples/active_record/db/migrate/002_create_categories.rb +10 -0
  16. data/examples/active_record/db/migrate/003_create_books.rb +16 -0
  17. data/examples/active_record/db/migrate/004_create_tags.rb +10 -0
  18. data/examples/active_record/db/migrate/005_create_books_tags.rb +11 -0
  19. data/examples/active_record/db/migrate/006_create_chapters.rb +12 -0
  20. data/examples/active_record/db/schema.rb +61 -0
  21. data/examples/active_record/db/seeds.rb +0 -0
  22. data/examples/activeresource/Gemfile +10 -0
  23. data/examples/activeresource/Gemfile.lock +68 -0
  24. data/examples/activeresource/app.rb +125 -0
  25. data/examples/activeresource/config.ru +9 -0
  26. data/examples/activeresource/layout.slim +78 -0
  27. data/examples/misc/set_verb_options.rb +12 -0
  28. data/examples/mongoid/Gemfile +11 -0
  29. data/examples/mongoid/Gemfile.lock +68 -0
  30. data/examples/mongoid/Rakefile +3 -0
  31. data/examples/mongoid/app.rb +144 -0
  32. data/examples/mongoid/config.ru +8 -0
  33. data/examples/mongoid/database.yml +13 -0
  34. data/examples/mongoid/lib/tasks/db.rake +79 -0
  35. data/examples/ng-admin/index.html +168 -0
  36. data/examples/ng-admin/package.json +14 -0
  37. data/examples/sequel/Gemfile +14 -0
  38. data/examples/sequel/Gemfile.lock +54 -0
  39. data/examples/sequel/Rakefile +3 -0
  40. data/examples/sequel/app.rb +138 -0
  41. data/examples/sequel/config.ru +9 -0
  42. data/examples/sequel/database.yml +7 -0
  43. data/examples/sequel/db/migrate/001_create_authors.rb +15 -0
  44. data/examples/sequel/db/migrate/002_create_categories.rb +14 -0
  45. data/examples/sequel/db/migrate/003_create_books.rb +20 -0
  46. data/examples/sequel/db/migrate/004_create_tags.rb +14 -0
  47. data/examples/sequel/db/migrate/005_create_books_tags.rb +15 -0
  48. data/examples/sequel/db/migrate/006_create_chapters.rb +16 -0
  49. data/examples/sequel/lib/tasks/db.rake +44 -0
  50. data/lib/sinatra-rest-api/actions.rb +2 -1
  51. data/sinatra-rest-api.gemspec +24 -0
  52. data/spec/action_create_spec.rb +164 -0
  53. data/spec/action_delete_spec.rb +32 -0
  54. data/spec/action_list_spec.rb +31 -0
  55. data/spec/action_read_spec.rb +28 -0
  56. data/spec/action_update_spec.rb +98 -0
  57. data/spec/app_active_record_spec.rb +53 -0
  58. data/spec/app_mongoid_spec.rb +46 -0
  59. data/spec/app_sequel_spec.rb +45 -0
  60. data/spec/spec_helper.rb +59 -0
  61. metadata +70 -3
@@ -0,0 +1,12 @@
1
+ require 'sinatra/base'
2
+
3
+ class TestApp < Sinatra::Application
4
+ options '*' do
5
+ response.headers['Access-Control-Allow-Origin'] = '*'
6
+ response.headers['Access-Control-Allow-Methods'] = 'HEAD,GET,PUT,DELETE,OPTIONS'
7
+ response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
8
+ halt 200
9
+ end
10
+
11
+ run! if app_file == $PROGRAM_NAME # = $0 ## starts the server if executed directly by ruby
12
+ end
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'pry'
4
+
5
+ gem 'sinatra'
6
+ gem 'sinatra-contrib'
7
+ gem 'thin'
8
+
9
+ gem 'mongoid'
10
+
11
+ gem 'sinatra-rest-api' #, path: '../../../sinatra-rest-api'
@@ -0,0 +1,68 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activemodel (5.1.4)
5
+ activesupport (= 5.1.4)
6
+ activesupport (5.1.4)
7
+ concurrent-ruby (~> 1.0, >= 1.0.2)
8
+ i18n (~> 0.7)
9
+ minitest (~> 5.1)
10
+ tzinfo (~> 1.1)
11
+ backports (3.8.0)
12
+ bson (4.2.2)
13
+ coderay (1.1.2)
14
+ concurrent-ruby (1.0.5)
15
+ daemons (1.2.4)
16
+ eventmachine (1.2.5)
17
+ i18n (0.8.6)
18
+ method_source (0.9.0)
19
+ minitest (5.10.3)
20
+ mongo (2.4.3)
21
+ bson (>= 4.2.1, < 5.0.0)
22
+ mongoid (6.2.1)
23
+ activemodel (~> 5.1)
24
+ mongo (>= 2.4.1, < 3.0.0)
25
+ multi_json (1.12.2)
26
+ mustermann (1.0.1)
27
+ pry (0.11.1)
28
+ coderay (~> 1.1.0)
29
+ method_source (~> 0.9.0)
30
+ rack (2.0.3)
31
+ rack-protection (2.0.0)
32
+ rack
33
+ sinatra (2.0.0)
34
+ mustermann (~> 1.0)
35
+ rack (~> 2.0)
36
+ rack-protection (= 2.0.0)
37
+ tilt (~> 2.0)
38
+ sinatra-contrib (2.0.0)
39
+ backports (>= 2.0)
40
+ multi_json
41
+ mustermann (~> 1.0)
42
+ rack-protection (= 2.0.0)
43
+ sinatra (= 2.0.0)
44
+ tilt (>= 1.3, < 3)
45
+ sinatra-rest-api (0.1.2)
46
+ sinatra (~> 2.0, > 1.4.0)
47
+ thin (1.7.2)
48
+ daemons (~> 1.0, >= 1.0.9)
49
+ eventmachine (~> 1.0, >= 1.0.4)
50
+ rack (>= 1, < 3)
51
+ thread_safe (0.3.6)
52
+ tilt (2.0.8)
53
+ tzinfo (1.2.3)
54
+ thread_safe (~> 0.1)
55
+
56
+ PLATFORMS
57
+ ruby
58
+
59
+ DEPENDENCIES
60
+ mongoid
61
+ pry
62
+ sinatra
63
+ sinatra-contrib
64
+ sinatra-rest-api
65
+ thin
66
+
67
+ BUNDLED WITH
68
+ 1.15.4
@@ -0,0 +1,3 @@
1
+ # $VERBOSE = true
2
+
3
+ Dir.glob( 'lib/tasks/*.rake' ).each { |r| load r }
@@ -0,0 +1,144 @@
1
+ # require 'pry'
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ require 'sinatra/base'
7
+
8
+ require 'mongoid'
9
+
10
+ require 'sinatra-rest-api'
11
+
12
+ # MongoidTest App
13
+ module MongoidTest
14
+ env = ENV['RACK_ENV'] || 'development'
15
+
16
+ Mongoid.load!( File.expand_path( '../database.yml', __FILE__ ), env.to_sym )
17
+
18
+ # An author
19
+ class Author
20
+ include Mongoid::Document
21
+ include Mongoid::Timestamps
22
+
23
+ field :name, type: String
24
+ field :email, type: String
25
+
26
+ has_many :books
27
+ # has_many :books, inverse_of: :author
28
+
29
+ validates :name, presence: true
30
+ validates :email, presence: true, uniqueness: true
31
+ end
32
+
33
+ # A book
34
+ class Book
35
+ include Mongoid::Document
36
+ include Mongoid::Timestamps
37
+
38
+ # Fields
39
+ field :title, type: String
40
+ field :description, type: String
41
+ field :pages, type: Integer
42
+ field :price, type: Float
43
+ field :dt, type: DateTime
44
+
45
+ # Relations
46
+ belongs_to :category, optional: true # optional disables required relations
47
+ belongs_to :author, optional: true
48
+ has_and_belongs_to_many :tags
49
+ has_many :chapters
50
+
51
+ accepts_nested_attributes_for :category
52
+ accepts_nested_attributes_for :author
53
+ accepts_nested_attributes_for :tags
54
+
55
+ # has_one :author, inverse_of: :books
56
+ # has_one :category, inverse_of: :book
57
+
58
+ # embeds_one :author
59
+ # embeds_one :category
60
+
61
+ validates :title, presence: true
62
+ end
63
+
64
+ # A category
65
+ class Category
66
+ include Mongoid::Document
67
+ include Mongoid::Timestamps
68
+
69
+ field :name, type: String
70
+
71
+ has_many :books
72
+ # has_many :books, inverse_of: :category
73
+
74
+ # embedded_in :book
75
+ end
76
+
77
+ # A tag
78
+ class Tag
79
+ include Mongoid::Document
80
+ include Mongoid::Timestamps
81
+
82
+ field :name, type: String
83
+
84
+ has_and_belongs_to_many :books
85
+ end
86
+
87
+ # A chapter
88
+ class Chapter
89
+ include Mongoid::Document
90
+ include Mongoid::Timestamps
91
+
92
+ belongs_to :book
93
+
94
+ field :title, type: String
95
+
96
+ validates :title, presence: true
97
+ end
98
+
99
+ ##############################################################################
100
+
101
+ # App main class
102
+ class App < Sinatra::Application
103
+ register Sinatra::RestApi
104
+
105
+ # model Author, actions: { list: { verb: :post }, read: { verb: :post } }
106
+ # model Author, actions: { list: true, read: true }
107
+
108
+ # set :restapi_request_type, :json
109
+
110
+ resource Author, singular: 'writer' # , plural: 'writers'
111
+ resource Book
112
+ resource Category do
113
+ actions [ :list, :read ]
114
+ end
115
+ resource Chapter
116
+ resource Tag # , actions: [ :list, :read ]
117
+
118
+ before do
119
+ content_type :json
120
+ headers( 'Access-Control-Allow-Origin' => '*',
121
+ 'Access-Control-Allow-Methods' => Sinatra::RestApi::Router::VERBS.join(',').upcase,
122
+ 'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, Token',
123
+ 'Access-Control-Expose-Headers' => 'X-Total-Count' )
124
+ end
125
+
126
+ options '*' do
127
+ response.headers['Access-Control-Allow-Origin'] = '*'
128
+ response.headers['Access-Control-Allow-Methods'] = 'HEAD,GET,PUT,DELETE,OPTIONS'
129
+ response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
130
+ halt 200
131
+ end
132
+
133
+ get '/' do
134
+ Sinatra::RestApi::Router.list_routes.to_json
135
+ end
136
+
137
+ not_found do
138
+ return if JSON.parse( body[0] ).include?( 'error' ) rescue nil # Error already defined
139
+ [ 404, { message: 'This is not the page you are looking for...' }.to_json ]
140
+ end
141
+
142
+ run! if app_file == $PROGRAM_NAME # = $0 ## starts the server if executed directly by ruby
143
+ end
144
+ end
@@ -0,0 +1,8 @@
1
+ # config.ru (run with rackup)
2
+
3
+ # Launch with:
4
+ # rackup -o 0.0.0.0 -p 4567
5
+
6
+ require './app'
7
+
8
+ run MongoidTest::App
@@ -0,0 +1,13 @@
1
+ development:
2
+ clients:
3
+ default:
4
+ database: sinatra-rest-api
5
+ hosts:
6
+ - localhost:27017
7
+
8
+ test:
9
+ clients:
10
+ default:
11
+ database: sinatra-rest-api_test
12
+ hosts:
13
+ - localhost:27017
@@ -0,0 +1,79 @@
1
+ # require 'mongoid'
2
+
3
+ load 'mongoid/tasks/database.rake'
4
+
5
+ # spec = Gem::Specification.find_by_name 'mongoid'
6
+ # load "#{spec.gem_dir}/lib/mongoid/railties/database.rake"
7
+
8
+ # Rake::Task.define_task( :environment )
9
+
10
+ namespace :db do
11
+ unless Rake::Task.task_defined?('db:drop')
12
+ desc 'Drops all the collections for the database for the current Rails.env'
13
+ task drop: 'mongoid:drop'
14
+ end
15
+
16
+ unless Rake::Task.task_defined?('db:purge')
17
+ desc 'Drop all collections except the system collections'
18
+ task purge: 'mongoid:purge'
19
+ end
20
+
21
+ unless Rake::Task.task_defined?('db:seed')
22
+ # if another ORM has defined db:seed, don't run it twice.
23
+ desc 'Load the seed data from db/seeds.rb'
24
+ task seed: :environment do
25
+ seed_file = File.join(Rails.root, 'db', 'seeds.rb')
26
+ load(seed_file) if File.exist?(seed_file)
27
+ end
28
+ end
29
+
30
+ unless Rake::Task.task_defined?('db:setup')
31
+ desc 'Create the database, and initialize with the seed data'
32
+ task setup: [ 'db:create', 'mongoid:create_indexes', 'db:seed' ]
33
+ end
34
+
35
+ unless Rake::Task.task_defined?('db:reset')
36
+ desc 'Delete data and loads the seeds'
37
+ task reset: [ 'db:drop', 'db:seed' ]
38
+ end
39
+
40
+ unless Rake::Task.task_defined?('db:create')
41
+ task create: :environment do
42
+ # noop
43
+ end
44
+ end
45
+
46
+ unless Rake::Task.task_defined?('db:migrate')
47
+ task migrate: :environment do
48
+ # noop
49
+ end
50
+ end
51
+
52
+ unless Rake::Task.task_defined?('db:schema:load')
53
+ namespace :schema do
54
+ task :load do
55
+ # noop
56
+ end
57
+ end
58
+ end
59
+
60
+ unless Rake::Task.task_defined?('db:test:prepare')
61
+ namespace :test do
62
+ task prepare: 'mongoid:create_indexes'
63
+ end
64
+ end
65
+
66
+ unless Rake::Task.task_defined?('db:create_indexes')
67
+ task create_indexes: 'mongoid:create_indexes'
68
+ end
69
+
70
+ unless Rake::Task.task_defined?('db:remove_indexes')
71
+ task remove_indexes: 'mongoid:remove_indexes'
72
+ end
73
+
74
+ namespace :mongoid do
75
+ task :load_models do
76
+ ::Rails.application.eager_load! if defined?(::Rails)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,168 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Test ng-admin</title>
5
+ <link rel="stylesheet" href="node_modules/ng-admin/build/ng-admin.min.css">
6
+ </head>
7
+ <body ng-app="myApp" ng-strict-di>
8
+ <!-- <script src="node_modules/angular/angular.js"></script>
9
+ <script src="ng-admin/src/javascripts/ng-admin.js"></script> -->
10
+ <script src="node_modules/ng-admin/build/ng-admin.min.js"></script>
11
+ <!-- <script src="node_modules/ng-admin/lib/javascripts/ng-admin.js"></script> -->
12
+ <div ui-view="ng-admin"></div>
13
+ <script type="text/javascript">
14
+ var LIMIT = 10;
15
+
16
+ var entities = {
17
+ 'writers': 'writer',
18
+ 'books': 'book',
19
+ 'categories': 'category',
20
+ 'chapters': 'chapter',
21
+ 'tags': 'tag'
22
+ };
23
+ var myApp = angular.module( 'myApp', [ 'ng-admin' ] );
24
+ myApp.config( [ 'NgAdminConfigurationProvider', function( NgAdminConfigurationProvider ) {
25
+ var nga = NgAdminConfigurationProvider;
26
+ // create an admin application
27
+ var admin = nga.application( 'My First Admin' ).baseApiUrl( 'http://127.0.0.1:3000/' );
28
+
29
+ var authors = nga.entity('writers');
30
+ var books = nga.entity('books');
31
+ var categories = nga.entity('categories' ).readOnly();
32
+ var chapters = nga.entity('chapters');
33
+ var tags = nga.entity('tags');
34
+
35
+ // Authors
36
+ authors.listView().fields([
37
+ nga.field( 'id' ),
38
+ nga.field( 'name' ).isDetailLink( true ),
39
+ nga.field( 'email' )
40
+ ]).listActions( [ 'show', 'delete' ] ).perPage( LIMIT );
41
+ authors.showView().fields([
42
+ nga.field( 'name' ),
43
+ nga.field( 'email' ),
44
+ nga.field( 'books', 'embedded_list' ).targetEntity( books ).targetFields( [ nga.field( 'title' ) ] )
45
+ ]);
46
+ authors.creationView().fields([
47
+ nga.field( 'name' ),
48
+ nga.field( 'email' )
49
+ ]);
50
+ authors.editionView().fields( authors.creationView().fields() );
51
+
52
+ // Books
53
+ books.listView().fields([
54
+ nga.field( 'id' ),
55
+ nga.field( 'title' ).isDetailLink(true),
56
+ // nga.field( 'description', 'text' ),
57
+ nga.field( 'pages', 'number' ),
58
+ nga.field( 'price', 'number' ).format( '$0,0.00' ),
59
+ // nga.field( 'dt', 'date' ),
60
+ nga.field( 'author_id', 'reference' ).targetEntity( authors ).targetField( nga.field( 'name' ) ),
61
+ nga.field( 'category_id', 'reference' ).targetEntity( categories ).targetField( nga.field( 'name' ) )
62
+ ]).listActions( [ 'show', 'delete' ] ).perPage( LIMIT );
63
+ books.showView().fields([
64
+ // nga.field( 'id' ),
65
+ nga.field( 'title' ).isDetailLink( true ),
66
+ nga.field( 'description', 'text' ),
67
+ nga.field( 'pages', 'number' ),
68
+ nga.field( 'price', 'number' ).format( '$0,0.00' ),
69
+ // nga.field( 'dt', 'date' ),
70
+ nga.field( 'author_id', 'reference' ).targetEntity( authors ).targetField( nga.field( 'name' ) ),
71
+ nga.field( 'category_id', 'reference' ).targetEntity( categories ).targetField( nga.field( 'name' ) ),
72
+ nga.field( 'chapters', 'embedded_list' ).targetEntity( chapters ).targetFields( [ nga.field( 'title' ) ] ),
73
+ nga.field( 'tags', 'embedded_list' ).targetEntity( tags ).targetFields( [ nga.field( 'name' ) ] )
74
+ ]);
75
+ books.creationView().fields([
76
+ // nga.field( 'id' ),
77
+ nga.field( 'title' ).isDetailLink( true ),
78
+ nga.field( 'description', 'text' ),
79
+ nga.field( 'pages', 'number' ),
80
+ nga.field( 'price', 'number' ).format( '$0,0.00' ),
81
+ // nga.field( 'dt', 'date' ),
82
+ nga.field( 'author_id', 'reference' ).targetEntity( authors ).targetField( nga.field( 'name' ) ),
83
+ nga.field( 'category_id', 'reference' ).targetEntity( categories ).targetField( nga.field( 'name' ) ),
84
+ // nga.field( 'chapters', 'referenced_list' ).targetEntity( chapters ).targetReferenceField( 'book_id' ).targetFields( [ nga.field( 'id' ), nga.field( 'title' ) ] ) // NO
85
+
86
+ // nga.field( 'chapters', 'embedded_list' ).targetEntity( chapters ).targetFields( [ nga.field( 'title' ) ] ) // NO
87
+
88
+ // nga.field( 'chapters', 'reference_many' ).targetEntity( chapters ).targetField( nga.field( 'title' ) )
89
+ // .singleApiCall( function( ids ) { return {'id': ids }; })
90
+ // nga.field( 'chapters', 'reference_many' ).targetEntity( chapters ).targetField( nga.field( 'title' ) )
91
+ // nga.field( 'chapters', 'reference_many' ).targetEntity( chapters ).targetField( nga.field( 'title' ) ).singleApiCall( function( tagIds ) {
92
+ // return { '_where': "['id IN ?',['1','2']]" };
93
+ // })
94
+ // _where=["id IN ?",["1","2"]]
95
+ ]);
96
+ books.editionView().fields( books.creationView().fields() );
97
+
98
+ // Categories
99
+ categories.listView().fields([
100
+ nga.field( 'id' ),
101
+ nga.field( 'name' ).isDetailLink( true )
102
+ ]).listActions( [ 'show' ] ).perPage( LIMIT );
103
+ categories.showView().fields([
104
+ nga.field( 'name' ),
105
+ nga.field( 'books', 'embedded_list' ).targetEntity( books ).targetFields( [ nga.field( 'title' ) ] )
106
+ ]);
107
+
108
+ // Chapters
109
+ chapters.listView().fields([
110
+ nga.field( 'id' ),
111
+ nga.field( 'title' ).isDetailLink( true )
112
+ ]).listActions( [ 'show', 'delete' ] ).perPage( LIMIT );
113
+ chapters.showView().fields([
114
+ nga.field( 'id' ),
115
+ nga.field( 'title' ),
116
+ nga.field( 'book_id', 'reference' ).targetEntity( books ).targetField( nga.field( 'title' ) )
117
+ ]);
118
+ chapters.creationView().fields([
119
+ nga.field( 'title' )
120
+ ]);
121
+ chapters.editionView().fields( chapters.creationView().fields() );
122
+
123
+ // Tags
124
+ tags.listView().fields([
125
+ nga.field( 'id' ),
126
+ nga.field( 'name' ).isDetailLink( true )
127
+ ]).listActions( [ 'show', 'delete' ] ).perPage( LIMIT );
128
+ tags.showView().fields([
129
+ nga.field( 'id' ),
130
+ nga.field( 'name' ),
131
+ nga.field( 'books', 'embedded_list' ).targetEntity( books ).targetFields( [ nga.field( 'title' ) ] )
132
+ ]);
133
+ tags.creationView().fields([
134
+ nga.field( 'name' )
135
+ ]);
136
+ tags.editionView().fields( tags.creationView().fields() );
137
+
138
+ admin.addEntity( authors );
139
+ admin.addEntity( books );
140
+ admin.addEntity( categories );
141
+ admin.addEntity( chapters );
142
+ admin.addEntity( tags );
143
+
144
+ nga.configure( admin );
145
+ }]);
146
+
147
+ myApp.config( [ 'RestangularProvider', function( RestangularProvider ) {
148
+ RestangularProvider.addFullRequestInterceptor( function( element, operation, what, url, headers, params, httpConfig ) {
149
+ if( operation == 'getList' ) {
150
+ if( params._page && params._perPage ) {
151
+ params.limit = params._perPage;
152
+ params.offset = ( params._page - 1 )* params.limit;
153
+ delete params._page;
154
+ delete params._perPage;
155
+ }
156
+ }
157
+ else if( operation == 'post' || operation == 'put' ) {
158
+ ret = {}
159
+ ret[entities[what]] = element;
160
+ return { element: ret }
161
+ }
162
+ // return { element: element };
163
+ return { params: params };
164
+ });
165
+ }]);
166
+ </script>
167
+ </body>
168
+ </html>