sinatra-rest-api 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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,14 @@
1
+ {
2
+ "name": "test-ng-admin",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.html",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Mat",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "ng-admin": "^1.0.0-beta3"
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'pry'
4
+
5
+ gem 'sinatra'
6
+ gem 'sinatra-contrib'
7
+
8
+ gem 'rake'
9
+ gem 'thin'
10
+
11
+ gem 'sequel'
12
+ gem 'sqlite3'
13
+
14
+ gem 'sinatra-rest-api' # , path: '../../../sinatra-rest-api'
@@ -0,0 +1,54 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ backports (3.8.0)
5
+ coderay (1.1.2)
6
+ daemons (1.2.4)
7
+ eventmachine (1.2.5)
8
+ method_source (0.9.0)
9
+ multi_json (1.12.2)
10
+ mustermann (1.0.1)
11
+ pry (0.11.1)
12
+ coderay (~> 1.1.0)
13
+ method_source (~> 0.9.0)
14
+ rack (2.0.3)
15
+ rack-protection (2.0.0)
16
+ rack
17
+ rake (12.1.0)
18
+ sequel (5.0.0)
19
+ sinatra (2.0.0)
20
+ mustermann (~> 1.0)
21
+ rack (~> 2.0)
22
+ rack-protection (= 2.0.0)
23
+ tilt (~> 2.0)
24
+ sinatra-contrib (2.0.0)
25
+ backports (>= 2.0)
26
+ multi_json
27
+ mustermann (~> 1.0)
28
+ rack-protection (= 2.0.0)
29
+ sinatra (= 2.0.0)
30
+ tilt (>= 1.3, < 3)
31
+ sinatra-rest-api (0.1.2)
32
+ sinatra (~> 2.0, > 1.4.0)
33
+ sqlite3 (1.3.13)
34
+ thin (1.7.2)
35
+ daemons (~> 1.0, >= 1.0.9)
36
+ eventmachine (~> 1.0, >= 1.0.4)
37
+ rack (>= 1, < 3)
38
+ tilt (2.0.8)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ pry
45
+ rake
46
+ sequel
47
+ sinatra
48
+ sinatra-contrib
49
+ sinatra-rest-api
50
+ sqlite3
51
+ thin
52
+
53
+ BUNDLED WITH
54
+ 1.15.4
@@ -0,0 +1,3 @@
1
+ # $VERBOSE = true
2
+
3
+ Dir.glob( 'lib/tasks/*.rake' ).each { |r| load r }
@@ -0,0 +1,138 @@
1
+ require 'pry'
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ require 'sinatra/base'
7
+
8
+ require 'sequel'
9
+ require 'sqlite3'
10
+
11
+ require 'sinatra-rest-api'
12
+
13
+ # SequelTest App
14
+ module SequelTest
15
+ env = ENV['RACK_ENV'] || 'development'
16
+ CONF = YAML.load( File.read( File.expand_path( '../database.yml', __FILE__ ) ) )
17
+ CONF[env]['database'] = File.expand_path( '../' + CONF[env]['database'], __FILE__ )
18
+ DB = Sequel.connect( CONF[env] )
19
+
20
+ unless ENV['SEQUEL_RECREATE_DB'].nil?
21
+ require 'rake'
22
+ FileUtils.rm CONF[env]['database'], force: true
23
+ FileUtils.touch CONF[env]['database']
24
+ load File.join( File.expand_path( '../lib', __FILE__ ), 'tasks', 'db.rake' )
25
+ Rake::Task['db:migrate'].invoke
26
+ end
27
+
28
+ Sequel::Model.plugin :association_pks
29
+ Sequel::Model.plugin :json_serializer
30
+ Sequel::Model.plugin :nested_attributes
31
+ Sequel::Model.plugin :timestamps
32
+ Sequel::Model.plugin :validation_helpers
33
+
34
+ # An author
35
+ class Author < Sequel::Model
36
+ plugin :timestamps, update_on_create: true
37
+
38
+ one_to_many :books
39
+
40
+ def validate
41
+ super
42
+ validates_presence [ :name, :email ]
43
+ validates_unique :email
44
+ end
45
+ end
46
+
47
+ # A book
48
+ class Book < Sequel::Model
49
+ plugin :timestamps, update_on_create: true
50
+
51
+ many_to_one :author
52
+ many_to_one :category
53
+ many_to_many :tags
54
+ one_to_many :chapters
55
+
56
+ nested_attributes :author
57
+ nested_attributes :category # , unmatched_pk: :create
58
+ nested_attributes :tags # , unmatched_pk: :create
59
+ nested_attributes :chapters
60
+
61
+ def validate
62
+ super
63
+ validates_presence :title
64
+ end
65
+ end
66
+
67
+ # A category
68
+ class Category < Sequel::Model
69
+ plugin :timestamps, update_on_create: true
70
+
71
+ one_to_many :books
72
+ end
73
+
74
+ # A tag
75
+ class Tag < Sequel::Model
76
+ plugin :timestamps, update_on_create: true
77
+
78
+ many_to_many :books
79
+ end
80
+
81
+ # A chapter
82
+ class Chapter < Sequel::Model
83
+ plugin :timestamps, update_on_create: true
84
+
85
+ many_to_one :book
86
+
87
+ def validate
88
+ super
89
+ validates_presence :title
90
+ end
91
+ end
92
+
93
+ ##############################################################################
94
+
95
+ # App entry point
96
+ class App < Sinatra::Application
97
+ register Sinatra::RestApi
98
+
99
+ # model Author, actions: { list: { verb: :post }, read: { verb: :post } }
100
+ # model Author, actions: { list: true, read: true }
101
+
102
+ set :restapi_request_type, :json
103
+
104
+ resource Author, singular: 'writer' # , plural: 'writers'
105
+ resource Book
106
+ resource Category do
107
+ actions [ :list, :read ]
108
+ end
109
+ resource Chapter
110
+ resource Tag # , actions: [ :list, :read ]
111
+
112
+ before do
113
+ content_type :json
114
+ headers( 'Access-Control-Allow-Origin' => '*',
115
+ 'Access-Control-Allow-Methods' => Sinatra::RestApi::Router::VERBS.join(',').upcase,
116
+ 'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, Token',
117
+ 'Access-Control-Expose-Headers' => 'X-Total-Count' )
118
+ end
119
+
120
+ options '*' do
121
+ response.headers['Access-Control-Allow-Origin'] = '*'
122
+ response.headers['Access-Control-Allow-Methods'] = 'HEAD,GET,PUT,DELETE,OPTIONS'
123
+ response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
124
+ halt 200
125
+ end
126
+
127
+ get '/' do
128
+ Sinatra::RestApi::Router.list_routes.to_json
129
+ end
130
+
131
+ not_found do
132
+ return if JSON.parse( body[0] ).include?( 'error' ) rescue nil # Error already defined
133
+ [ 404, { message: 'This is not the page you are looking for...' }.to_json ]
134
+ end
135
+
136
+ run! if app_file == $PROGRAM_NAME # = $0 ## starts the server if executed directly by ruby
137
+ end
138
+ end
@@ -0,0 +1,9 @@
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 App
9
+ run SequelTest::App
@@ -0,0 +1,7 @@
1
+ development:
2
+ adapter: sqlite
3
+ database: 'db/db.sqlite3'
4
+
5
+ test:
6
+ adapter: sqlite
7
+ database: 'db/test.sqlite3'
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :authors ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ String :email, null: false
7
+ Timestamp :created_at
8
+ Timestamp :updated_at
9
+ end
10
+ end
11
+
12
+ down do
13
+ drop_table( :authors )
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :categories ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ Timestamp :created_at
7
+ Timestamp :updated_at
8
+ end
9
+ end
10
+
11
+ down do
12
+ drop_table( :categories )
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :books ) do
4
+ primary_key :id
5
+ String :title, null: false
6
+ String :description, text: true
7
+ Integer :pages
8
+ Float :price
9
+ DateTime :dt
10
+ Integer :author_id
11
+ Integer :category_id
12
+ Timestamp :created_at
13
+ Timestamp :updated_at
14
+ end
15
+ end
16
+
17
+ down do
18
+ drop_table( :books )
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :tags ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ Timestamp :created_at
7
+ Timestamp :updated_at
8
+ end
9
+ end
10
+
11
+ down do
12
+ drop_table( :tags )
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :books_tags ) do
4
+ primary_key :id
5
+ Integer :book_id, null: false
6
+ Integer :tag_id, null: false
7
+ Timestamp :created_at
8
+ Timestamp :updated_at
9
+ end
10
+ end
11
+
12
+ down do
13
+ drop_table( :books_tags )
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :chapters ) do
4
+ primary_key :id
5
+ String :title, null: false
6
+ Integer :page
7
+ Integer :book_id, null: false
8
+ Timestamp :created_at
9
+ Timestamp :updated_at
10
+ end
11
+ end
12
+
13
+ down do
14
+ drop_table( :chapters )
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # require File.expand_path( '../../../app.rb', __FILE__ )
2
+ require 'fileutils'
3
+ require 'sequel'
4
+ require 'yaml'
5
+
6
+ Sequel.extension :migration
7
+
8
+ PATH_MIGRATIONS = File.expand_path( '../../../db/migrate', __FILE__ ).freeze unless defined? PATH_MIGRATIONS
9
+
10
+ def get_db( connect = true )
11
+ env = ENV['RACK_ENV'] || 'development'
12
+ conf = YAML.load( File.read( File.expand_path( '../../../database.yml', __FILE__ ) ) )
13
+ conf[env]['database'] = File.expand_path( '../../../' + conf[env]['database'], __FILE__ )
14
+ connect ? Sequel.connect( conf[env] ) : conf
15
+ end
16
+
17
+ namespace :db do
18
+ desc 'Create database'
19
+ task :create do |_t, _args|
20
+ puts 'Create DB' # unless ENV['RACK_ENV'] == 'test'
21
+ env = ENV['RACK_ENV'] || 'development'
22
+ conf = get_db( false )
23
+ FileUtils.touch conf[env]['database']
24
+ end
25
+
26
+ # Ex. rake db:migrate[5]
27
+ desc 'Run migrations'
28
+ task :migrate, [ :version ] do |_t, args|
29
+ # db = Sequel.connect(ENV.fetch('DATABASE_URL'))
30
+ if args[:version]
31
+ puts "Migrating to version #{args[:version]}" # unless ENV['RACK_ENV'] == 'test'
32
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS, target: args[:version].to_i)
33
+ else
34
+ puts 'Migrating to latest' # unless ENV['RACK_ENV'] == 'test'
35
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS )
36
+ end
37
+ end
38
+
39
+ desc 'Reset database'
40
+ task :reset do |_t, _args|
41
+ puts 'Reset DB' # unless ENV['RACK_ENV'] == 'test'
42
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS, target: 0 )
43
+ end
44
+ end
@@ -46,10 +46,11 @@ module Sinatra
46
46
  end
47
47
 
48
48
  def self.list( route_args, params, mapping )
49
- # TODO: option to enable X-Total-Count ?
49
+ # TODO: option to enable/disable X-Total-Count ?
50
50
  params[:_where] = params[:_where].nil? ? {} : JSON.parse( params[:_where] )
51
51
  route_args[:response].headers['X-Total-Count'] = mapping[:count].call( params ).to_s
52
52
  result = mapping[:list].call( params, route_args[:fields] )
53
+ result = result.limit( route_args[:options][:limit].to_i ) if route_args[:options].include?( :limit )
53
54
  [ 200, result.to_json( include: route_args[:options].include?( :include ) ? route_args[:options][:include] : mapping[:relations].call( nil ) ) ]
54
55
  end
55
56
 
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path( '../lib', __FILE__ )
3
+ $LOAD_PATH.unshift( lib ) unless $LOAD_PATH.include?( lib )
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'sinatra-rest-api'
7
+ spec.version = '0.1.4'
8
+ spec.authors = [ 'Mattia Roccoberton' ]
9
+ spec.email = 'mat@blocknot.es'
10
+ spec.homepage = 'https://github.com/blocknotes/sinatra-rest-api'
11
+ spec.summary = 'Sinatra REST API generator'
12
+ spec.description = 'Sinatra REST API generator: CRUD actions, nested resources, supports ActiveRecord, Sequel and Mongoid'
13
+ spec.platform = Gem::Platform::RUBY
14
+ spec.license = 'ISC'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+
23
+ spec.add_runtime_dependency 'sinatra', '> 1.4'
24
+ end
@@ -0,0 +1,164 @@
1
+ $VERBOSE = false
2
+
3
+ # require 'pry'
4
+
5
+ # Tests actions
6
+ module ActionTests
7
+ RSpec.shared_examples 'Action: create' do |models|
8
+ ##############################################################################
9
+
10
+ context 'Creating authors...' do
11
+ # path = '/authors'
12
+ path = '/writers' # renamed resource
13
+ random_authors = []
14
+ (1..( rand( 15 ) + 5 )).each do |_i|
15
+ random_authors.push( name: Faker::Book.author, email: Faker::Internet.email )
16
+ end
17
+ random_authors.uniq! { |a| a[:email] }
18
+
19
+ # action: create
20
+ it "should create #{random_authors.length} authors" do
21
+ random_authors.each do |author|
22
+ post path, writer: author
23
+ expect( last_response.status ).to eq( 201 ) # response = created ?
24
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true # id is returned ?
25
+ expect( models[:author].where( author ).count ).to eq( 1 ) # record exists ?
26
+ end
27
+ expect( models[:author].count ).to eq( random_authors.length ) # check number of authors created
28
+ end
29
+
30
+ it 'should not create authors with invalid data' do
31
+ authors = [
32
+ {},
33
+ { name: '', email: ' ' },
34
+ { email: 'aaa@bbb.ccc' },
35
+ { name: '', email: 'aaa@bbb.ccc' },
36
+ { name: ' ', email: 'aaa@bbb.ccc' },
37
+ { name: 'Dick' },
38
+ { name: 'Dick', email: '' },
39
+ { name: 'Dick', email: ' ' }
40
+ ]
41
+ authors.each do |author|
42
+ post path, writer: author
43
+ expect( last_response.status ).to eq( 400 )
44
+ end
45
+ end
46
+
47
+ # action: create
48
+ it 'no authors with same email' do
49
+ post path, writer: { name: 'Jane Doe', email: random_authors.sample[:email] }
50
+ expect( last_response.status ).to eq( 400 )
51
+ end
52
+ end
53
+
54
+ ##############################################################################
55
+
56
+ context 'Creating tags...' do
57
+ random_tags = []
58
+ (1..( rand( 6 ) + 3 ) ).each { |_i| random_tags.push( name: Faker::Hipster.word ) }
59
+ random_tags.uniq! { |t| t[:name] }
60
+
61
+ # action: create
62
+ it "should create #{random_tags.length} tags" do
63
+ random_tags.each do |tag|
64
+ post '/tags', tag: tag
65
+ expect( last_response.status ).to eq( 201 )
66
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true
67
+ expect( models[:tag].where( tag ).count ).to eq( 1 )
68
+ end
69
+ expect( models[:tag].count ).to eq( random_tags.length )
70
+ end
71
+ end
72
+
73
+ ##############################################################################
74
+
75
+ context 'Creating books...' do
76
+ random_books = []
77
+ (1..( rand( 4 ) + 2 )).each do |_i|
78
+ random_books.push( title: Faker::Book.title )
79
+ end
80
+ random_books.uniq! { |b| b[:title] }
81
+ (1..( rand( 4 ) + 2 )).each do |_i|
82
+ random_books.push( title: Faker::Hipster.sentence, pages: rand( 1000 ), price: rand * 20 )
83
+ end
84
+ new_cats = rand( 4 ) + 2
85
+ (1..new_cats).each do |_i|
86
+ random_books.push( title: Faker::Superhero.name, description: Faker::StarWars.quote, pages: rand( 1000 ), price: rand * 20, category_attributes: { name: Faker::Book.genre } )
87
+ end
88
+ new_tags = 2
89
+ (1..new_tags).each do |_i|
90
+ random_books.push( title: Faker::Superhero.name, description: Faker::StarWars.quote, pages: rand( 1000 ), price: rand * 20, tags_attributes: [ { name: Faker::Hipster.word } ] )
91
+ end
92
+
93
+ # action: create
94
+ it "should create #{random_books.length} books, #{new_cats} categories and #{new_tags} tags" do
95
+ tags_ids = models[:tag].all.map( &:id )
96
+ (1..( rand( 4 ) + 2 )).each do |_i|
97
+ random_books.push( title: Faker::University.name, pages: rand( 1000 ), price: rand * 20, tag_ids: tags_ids.sample( rand( 3 ) ) )
98
+ end
99
+ random_books.each do |book|
100
+ book[:author_id] = models[:author].all.sample.id if rand > 0.5
101
+ post '/books', book: book
102
+ expect( last_response.status ).to eq( 201 ) # check if the status is equal to created
103
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true # check if the id is returned
104
+ category_attributes = book.delete :category_attributes
105
+ tags_attributes = book.delete :tags_attributes
106
+ tag_ids = book.delete :tag_ids
107
+ expect( models[:book].where( book ).count ).to eq( 1 ) # check if book is created
108
+ # check the relations
109
+ row = models[:book].where( book ).first
110
+ expect( row.tags.map( &:id ).sort ).to eq( tag_ids.sort ) unless tag_ids.nil?
111
+ expect( row.tags.map( &:name ) ).to eq( tags_attributes.map { |a| a[:name] } ) unless tags_attributes.nil?
112
+ expect( row.category.name ).to eq( category_attributes[:name] ) unless category_attributes.nil?
113
+ end
114
+ expect( models[:book].count ).to eq( random_books.length )
115
+ expect( models[:category].count ).to eq( new_cats )
116
+ expect( models[:tag].count ).to eq( tags_ids.length + new_tags )
117
+ end
118
+ end
119
+
120
+ ##############################################################################
121
+
122
+ context 'Creating chapters...' do
123
+ random_chapters = []
124
+ (1..( rand( 10 ) + 2 )).each do |_i|
125
+ random_chapters.push( title: Faker::Superhero.name )
126
+ end
127
+ random_chapters.uniq! { |c| c[:title] }
128
+
129
+ # action: create
130
+ it "should create #{random_chapters.length} chapters" do
131
+ ids = models[:book].all.map( &:id )
132
+ random_chapters.each do |chapter|
133
+ chapter[:book_id] = ids.sample
134
+ post '/chapters', chapter: chapter
135
+ expect( last_response.status ).to eq( 201 )
136
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true
137
+ expect( models[:chapter].where( chapter ).count ).to eq( 1 )
138
+ end
139
+ expect( models[:chapter].count ).to eq( random_chapters.length )
140
+ end
141
+
142
+ it 'should not create a chapter without data' do
143
+ chapters = [
144
+ {},
145
+ { book_id: models[:book].last.id.to_s }
146
+ ]
147
+ chapters.each do |chapter|
148
+ post '/chapters', chapter: chapter
149
+ expect( last_response.status ).to eq( 400 )
150
+ end
151
+ end
152
+ end
153
+
154
+ ##############################################################################
155
+
156
+ context 'Creating categories...' do
157
+ # action: create
158
+ it 'should not create categories' do
159
+ post '/categories', category: { name: 'A cat' }
160
+ expect( last_response.status ).to eq( 404 )
161
+ end
162
+ end
163
+ end
164
+ end