sober_swag 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/config/rubocop_linter_action.yml +5 -0
  3. data/.github/workflows/lint.yml +15 -0
  4. data/.github/workflows/ruby.yml +23 -1
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +73 -1
  7. data/.ruby-version +1 -1
  8. data/Gemfile.lock +29 -5
  9. data/README.md +109 -0
  10. data/bin/console +15 -14
  11. data/docs/serializers.md +203 -0
  12. data/example/.rspec +1 -0
  13. data/example/.ruby-version +1 -1
  14. data/example/Gemfile +10 -6
  15. data/example/Gemfile.lock +96 -76
  16. data/example/app/controllers/people_controller.rb +37 -21
  17. data/example/app/controllers/posts_controller.rb +102 -0
  18. data/example/app/models/application_record.rb +3 -0
  19. data/example/app/models/person.rb +6 -0
  20. data/example/app/models/post.rb +9 -0
  21. data/example/app/output_objects/person_errors_output_object.rb +5 -0
  22. data/example/app/output_objects/person_output_object.rb +15 -0
  23. data/example/app/output_objects/post_output_object.rb +10 -0
  24. data/example/bin/bundle +24 -20
  25. data/example/bin/rails +1 -1
  26. data/example/bin/rake +1 -1
  27. data/example/config/application.rb +11 -7
  28. data/example/config/environments/development.rb +0 -1
  29. data/example/config/environments/production.rb +3 -3
  30. data/example/config/puma.rb +5 -5
  31. data/example/config/routes.rb +3 -0
  32. data/example/config/spring.rb +4 -4
  33. data/example/db/migrate/20200311152021_create_people.rb +0 -1
  34. data/example/db/migrate/20200603172347_create_posts.rb +11 -0
  35. data/example/db/schema.rb +16 -7
  36. data/example/spec/rails_helper.rb +64 -0
  37. data/example/spec/requests/people/create_spec.rb +52 -0
  38. data/example/spec/requests/people/get_spec.rb +35 -0
  39. data/example/spec/requests/people/index_spec.rb +69 -0
  40. data/example/spec/spec_helper.rb +94 -0
  41. data/lib/sober_swag.rb +6 -3
  42. data/lib/sober_swag/compiler/error.rb +2 -0
  43. data/lib/sober_swag/compiler/path.rb +2 -5
  44. data/lib/sober_swag/compiler/paths.rb +0 -1
  45. data/lib/sober_swag/compiler/type.rb +28 -15
  46. data/lib/sober_swag/controller.rb +16 -11
  47. data/lib/sober_swag/controller/route.rb +18 -21
  48. data/lib/sober_swag/controller/undefined_body_error.rb +3 -0
  49. data/lib/sober_swag/controller/undefined_path_error.rb +3 -0
  50. data/lib/sober_swag/controller/undefined_query_error.rb +3 -0
  51. data/lib/sober_swag/input_object.rb +28 -0
  52. data/lib/sober_swag/nodes/array.rb +1 -1
  53. data/lib/sober_swag/nodes/base.rb +2 -4
  54. data/lib/sober_swag/nodes/binary.rb +2 -1
  55. data/lib/sober_swag/nodes/enum.rb +4 -2
  56. data/lib/sober_swag/nodes/list.rb +0 -1
  57. data/lib/sober_swag/nodes/primitive.rb +6 -5
  58. data/lib/sober_swag/output_object.rb +102 -0
  59. data/lib/sober_swag/output_object/definition.rb +30 -0
  60. data/lib/sober_swag/{blueprint → output_object}/field.rb +14 -4
  61. data/lib/sober_swag/{blueprint → output_object}/field_syntax.rb +1 -1
  62. data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
  63. data/lib/sober_swag/parser.rb +5 -3
  64. data/lib/sober_swag/serializer.rb +5 -2
  65. data/lib/sober_swag/serializer/array.rb +12 -0
  66. data/lib/sober_swag/serializer/base.rb +50 -1
  67. data/lib/sober_swag/serializer/conditional.rb +15 -2
  68. data/lib/sober_swag/serializer/field_list.rb +29 -6
  69. data/lib/sober_swag/serializer/mapped.rb +12 -2
  70. data/lib/sober_swag/serializer/meta.rb +35 -0
  71. data/lib/sober_swag/serializer/optional.rb +17 -2
  72. data/lib/sober_swag/serializer/primitive.rb +4 -1
  73. data/lib/sober_swag/server.rb +83 -0
  74. data/lib/sober_swag/types.rb +3 -0
  75. data/lib/sober_swag/version.rb +1 -1
  76. data/sober_swag.gemspec +6 -4
  77. metadata +77 -44
  78. data/example/person.json +0 -4
  79. data/example/test/controllers/.keep +0 -0
  80. data/example/test/fixtures/.keep +0 -0
  81. data/example/test/fixtures/files/.keep +0 -0
  82. data/example/test/fixtures/people.yml +0 -11
  83. data/example/test/integration/.keep +0 -0
  84. data/example/test/models/.keep +0 -0
  85. data/example/test/models/person_test.rb +0 -7
  86. data/example/test/test_helper.rb +0 -13
  87. data/lib/sober_swag/blueprint.rb +0 -113
  88. data/lib/sober_swag/path.rb +0 -8
  89. data/lib/sober_swag/path/integer.rb +0 -21
  90. data/lib/sober_swag/path/lit.rb +0 -41
  91. data/lib/sober_swag/path/literal.rb +0 -29
  92. data/lib/sober_swag/path/param.rb +0 -33
@@ -1,3 +1,6 @@
1
+ ##
2
+ # Base class for all other models.
3
+ # Does nothing interesting.
1
4
  class ApplicationRecord < ActiveRecord::Base
2
5
  self.abstract_class = true
3
6
  end
@@ -1,2 +1,8 @@
1
+ ##
2
+ # Extremely basic person example record.
1
3
  class Person < ApplicationRecord
4
+ has_many :posts, dependent: :destroy
5
+
6
+ validates :first_name, presence: true
7
+ validates :last_name, presence: true
2
8
  end
@@ -0,0 +1,9 @@
1
+ ##
2
+ # Extremely basic post example record.
3
+ # This is a blog post! Wow.
4
+ class Post < ApplicationRecord
5
+ belongs_to :person
6
+
7
+ validates :title, presence: true
8
+ validates :body, presence: true
9
+ end
@@ -0,0 +1,5 @@
1
+ PersonErrorsOutputObject = SoberSwag::OutputObject.define do
2
+ identifier 'PersonErrors'
3
+ field :first_name, primitive(:String).array.optional
4
+ field :last_name, primitive(:String).array.optional
5
+ end
@@ -0,0 +1,15 @@
1
+ PersonOutputObject = SoberSwag::OutputObject.define do
2
+ identifier 'Person'
3
+ field :id, primitive(:Integer).meta(description: 'Unique ID')
4
+ field :first_name, primitive(:String).meta(description: <<~MARKDOWN)
5
+ This is the first name of a person.
6
+ Note that you can't use this as a unique identifier, and you really should understand how names work before using this.
7
+ [Falsehoods programmers believe about names](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/)
8
+ is a good thing to read!
9
+ MARKDOWN
10
+ field :last_name, primitive(:String)
11
+
12
+ view :detail do
13
+ field :posts, -> { PostOutputObject.array }
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ PostOutputObject = SoberSwag::OutputObject.define do
2
+ identifier 'Post'
3
+ field :id, primitive(:Integer)
4
+ field :title, primitive(:String)
5
+ field :body, primitive(:String)
6
+
7
+ view :detail do
8
+ field :person, -> { PersonOutputObject.view(:base) }
9
+ end
10
+ end
@@ -8,7 +8,7 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require "rubygems"
11
+ require 'rubygems'
12
12
 
13
13
  m = Module.new do
14
14
  module_function
@@ -18,36 +18,36 @@ m = Module.new do
18
18
  end
19
19
 
20
20
  def env_var_version
21
- ENV["BUNDLER_VERSION"]
21
+ ENV['BUNDLER_VERSION']
22
22
  end
23
23
 
24
24
  def cli_arg_version
25
25
  return unless invoked_as_script? # don't want to hijack other binstubs
26
- return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
26
+ return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update`
27
+
27
28
  bundler_version = nil
28
29
  update_index = nil
29
30
  ARGV.each_with_index do |a, i|
30
- if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
- bundler_version = a
32
- end
31
+ bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
33
32
  next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
- bundler_version = $1
33
+
34
+ bundler_version = Regexp.last_match(1)
35
35
  update_index = i
36
36
  end
37
37
  bundler_version
38
38
  end
39
39
 
40
40
  def gemfile
41
- gemfile = ENV["BUNDLE_GEMFILE"]
41
+ gemfile = ENV['BUNDLE_GEMFILE']
42
42
  return gemfile if gemfile && !gemfile.empty?
43
43
 
44
- File.expand_path("../../Gemfile", __FILE__)
44
+ File.expand_path('../Gemfile', __dir__)
45
45
  end
46
46
 
47
47
  def lockfile
48
48
  lockfile =
49
49
  case File.basename(gemfile)
50
- when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
50
+ when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile)
51
51
  else "#{gemfile}.lock"
52
52
  end
53
53
  File.expand_path(lockfile)
@@ -55,15 +55,17 @@ m = Module.new do
55
55
 
56
56
  def lockfile_version
57
57
  return unless File.file?(lockfile)
58
+
58
59
  lockfile_contents = File.read(lockfile)
59
60
  return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
61
+
60
62
  Regexp.last_match(1)
61
63
  end
62
64
 
63
65
  def bundler_version
64
66
  @bundler_version ||=
65
67
  env_var_version || cli_arg_version ||
66
- lockfile_version
68
+ lockfile_version
67
69
  end
68
70
 
69
71
  def bundler_requirement
@@ -73,28 +75,32 @@ m = Module.new do
73
75
 
74
76
  requirement = bundler_gem_version.approximate_recommendation
75
77
 
76
- return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
78
+ return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0')
77
79
 
78
- requirement += ".a" if bundler_gem_version.prerelease?
80
+ requirement += '.a' if bundler_gem_version.prerelease?
79
81
 
80
82
  requirement
81
83
  end
82
84
 
83
85
  def load_bundler!
84
- ENV["BUNDLE_GEMFILE"] ||= gemfile
86
+ ENV['BUNDLE_GEMFILE'] ||= gemfile
85
87
 
86
88
  activate_bundler
87
89
  end
88
90
 
89
91
  def activate_bundler
90
92
  gem_error = activation_error_handling do
91
- gem "bundler", bundler_requirement
93
+ gem 'bundler', bundler_requirement
92
94
  end
93
95
  return if gem_error.nil?
96
+
94
97
  require_error = activation_error_handling do
95
- require "bundler/version"
98
+ require 'bundler/version'
99
+ end
100
+ if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
101
+ return
96
102
  end
97
- return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
103
+
98
104
  warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99
105
  exit 42
100
106
  end
@@ -109,6 +115,4 @@ end
109
115
 
110
116
  m.load_bundler!
111
117
 
112
- if m.invoked_as_script?
113
- load Gem.bin_path("bundler", "bundle")
114
- end
118
+ load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  begin
3
- load File.expand_path('../spring', __FILE__)
3
+ load File.expand_path('spring', __dir__)
4
4
  rescue LoadError => e
5
5
  raise unless e.message.include?('spring')
6
6
  end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  begin
3
- load File.expand_path('../spring', __FILE__)
3
+ load File.expand_path('spring', __dir__)
4
4
  rescue LoadError => e
5
5
  raise unless e.message.include?('spring')
6
6
  end
@@ -1,29 +1,33 @@
1
1
  require_relative 'boot'
2
2
 
3
- require "rails"
3
+ require 'rails'
4
4
  # Pick the frameworks you want:
5
- require "active_model/railtie"
6
- require "active_job/railtie"
7
- require "active_record/railtie"
5
+ require 'active_model/railtie'
6
+ require 'active_job/railtie'
7
+ require 'active_record/railtie'
8
8
  # require "active_storage/engine"
9
- require "action_controller/railtie"
9
+ require 'action_controller/railtie'
10
10
  # require "action_mailer/railtie"
11
11
  # require "action_mailbox/engine"
12
12
  # require "action_text/engine"
13
- require "action_view/railtie"
13
+ require 'action_view/railtie'
14
14
  # require "action_cable/engine"
15
15
  # require "sprockets/railtie"
16
- require "rails/test_unit/railtie"
16
+ require 'rails/test_unit/railtie'
17
17
 
18
18
  # Require the gems listed in Gemfile, including any gems
19
19
  # you've limited to :test, :development, or :production.
20
20
  Bundler.require(*Rails.groups)
21
21
 
22
22
  module Example
23
+ ##
24
+ # Toplevel rails app.
23
25
  class Application < Rails::Application
24
26
  # Initialize configuration defaults for originally generated Rails version.
25
27
  config.load_defaults 6.0
26
28
 
29
+ config.autoload_paths << 'app/blueprints'
30
+
27
31
  # Settings in config/environments/* take precedence over those specified here.
28
32
  # Application configuration can go into files in config/initializers
29
33
  # -- all .rb files in that directory are automatically loaded after loading
@@ -34,7 +34,6 @@ Rails.application.configure do
34
34
  # Highlight code that triggered database queries in logs.
35
35
  config.active_record.verbose_query_logs = true
36
36
 
37
-
38
37
  # Raises error for missing translations.
39
38
  # config.action_view.raise_on_missing_translations = true
40
39
 
@@ -11,7 +11,7 @@ Rails.application.configure do
11
11
  config.eager_load = true
12
12
 
13
13
  # Full error reports are disabled and caching is turned on.
14
- config.consider_all_requests_local = false
14
+ config.consider_all_requests_local = false
15
15
 
16
16
  # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
17
17
  # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
@@ -36,7 +36,7 @@ Rails.application.configure do
36
36
  config.log_level = :debug
37
37
 
38
38
  # Prepend all log lines with the following tags.
39
- config.log_tags = [ :request_id ]
39
+ config.log_tags = [:request_id]
40
40
 
41
41
  # Use a different cache store in production.
42
42
  # config.cache_store = :mem_cache_store
@@ -59,7 +59,7 @@ Rails.application.configure do
59
59
  # require 'syslog/logger'
60
60
  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
61
61
 
62
- if ENV["RAILS_LOG_TO_STDOUT"].present?
62
+ if ENV['RAILS_LOG_TO_STDOUT'].present?
63
63
  logger = ActiveSupport::Logger.new(STDOUT)
64
64
  logger.formatter = config.log_formatter
65
65
  config.logger = ActiveSupport::TaggedLogging.new(logger)
@@ -4,20 +4,20 @@
4
4
  # the maximum value specified for Puma. Default is set to 5 threads for minimum
5
5
  # and maximum; this matches the default thread size of Active Record.
6
6
  #
7
- max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8
- min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
7
+ max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
8
+ min_threads_count = ENV.fetch('RAILS_MIN_THREADS', max_threads_count)
9
9
  threads min_threads_count, max_threads_count
10
10
 
11
11
  # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
12
12
  #
13
- port ENV.fetch("PORT") { 3000 }
13
+ port ENV.fetch('PORT', 3000)
14
14
 
15
15
  # Specifies the `environment` that Puma will run in.
16
16
  #
17
- environment ENV.fetch("RAILS_ENV") { "development" }
17
+ environment ENV.fetch('RAILS_ENV') { 'development' }
18
18
 
19
19
  # Specifies the `pidfile` that Puma will use.
20
- pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
20
+ pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
21
21
 
22
22
  # Specifies the number of `workers` to boot in clustered mode.
23
23
  # Workers are forked web server processes. If using threads and workers together
@@ -1,6 +1,9 @@
1
1
  Rails.application.routes.draw do
2
+ resources :posts
2
3
  resources :people do
3
4
  get :swagger, on: :collection
4
5
  end
6
+
7
+ mount SoberSwag::Server.new(cache: -> { Rails.env.production? }), at: '/swagger'
5
8
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
6
9
  end
@@ -1,6 +1,6 @@
1
1
  Spring.watch(
2
- ".ruby-version",
3
- ".rbenv-vars",
4
- "tmp/restart.txt",
5
- "tmp/caching-dev.txt"
2
+ '.ruby-version',
3
+ '.rbenv-vars',
4
+ 'tmp/restart.txt',
5
+ 'tmp/caching-dev.txt'
6
6
  )
@@ -1,5 +1,4 @@
1
1
  class CreatePeople < ActiveRecord::Migration[6.0]
2
-
3
2
  def change
4
3
  create_table :people do |t|
5
4
  t.text :first_name, null: false
@@ -0,0 +1,11 @@
1
+ class CreatePosts < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :posts do |t|
4
+ t.references :person, null: false, foreign_key: true, index: true
5
+ t.text :title, null: false
6
+ t.text :body, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -10,14 +10,23 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2020_03_11_152021) do
13
+ ActiveRecord::Schema.define(version: 20_200_603_172_347) do
14
+ create_table 'people', force: :cascade do |t|
15
+ t.text 'first_name', null: false
16
+ t.text 'last_name', null: false
17
+ t.datetime 'date_of_birth'
18
+ t.datetime 'created_at', precision: 6, null: false
19
+ t.datetime 'updated_at', precision: 6, null: false
20
+ end
14
21
 
15
- create_table "people", force: :cascade do |t|
16
- t.text "first_name", null: false
17
- t.text "last_name", null: false
18
- t.datetime "date_of_birth"
19
- t.datetime "created_at", precision: 6, null: false
20
- t.datetime "updated_at", precision: 6, null: false
22
+ create_table 'posts', force: :cascade do |t|
23
+ t.integer 'person_id', null: false
24
+ t.text 'title', null: false
25
+ t.text 'body', null: false
26
+ t.datetime 'created_at', precision: 6, null: false
27
+ t.datetime 'updated_at', precision: 6, null: false
28
+ t.index ['person_id'], name: 'index_posts_on_person_id'
21
29
  end
22
30
 
31
+ add_foreign_key 'posts', 'people'
23
32
  end
@@ -0,0 +1,64 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ require 'spec_helper'
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+ require File.expand_path('../config/environment', __dir__)
5
+ # Prevent database truncation if the environment is production
6
+ abort('The Rails environment is running in production mode!') if Rails.env.production?
7
+ require 'rspec/rails'
8
+ # Add additional requires below this line. Rails is not loaded until this point!
9
+
10
+ # Requires supporting ruby files with custom matchers and macros, etc, in
11
+ # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
12
+ # run as spec files by default. This means that files in spec/support that end
13
+ # in _spec.rb will both be required and run as specs, causing the specs to be
14
+ # run twice. It is recommended that you do not name files matching this glob to
15
+ # end with _spec.rb. You can configure this pattern with the --pattern
16
+ # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
17
+ #
18
+ # The following line is provided for convenience purposes. It has the downside
19
+ # of increasing the boot-up time by auto-requiring all files in the support
20
+ # directory. Alternatively, in the individual `*_spec.rb` files, manually
21
+ # require only the support files necessary.
22
+ #
23
+ # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
24
+
25
+ # Checks for pending migrations and applies them before tests are run.
26
+ # If you are not using ActiveRecord, you can remove these lines.
27
+ begin
28
+ ActiveRecord::Migration.maintain_test_schema!
29
+ rescue ActiveRecord::PendingMigrationError => e
30
+ puts e.to_s.strip
31
+ exit 1
32
+ end
33
+ RSpec.configure do |config|
34
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
35
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
36
+
37
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
38
+ # examples within a transaction, remove the following line or assign false
39
+ # instead of true.
40
+ config.use_transactional_fixtures = true
41
+
42
+ # You can uncomment this line to turn off ActiveRecord support entirely.
43
+ # config.use_active_record = false
44
+
45
+ # RSpec Rails can automatically mix in different behaviours to your tests
46
+ # based on their file location, for example enabling you to call `get` and
47
+ # `post` in specs under `spec/controllers`.
48
+ #
49
+ # You can disable this behaviour by removing the line below, and instead
50
+ # explicitly tag your specs with their type, e.g.:
51
+ #
52
+ # RSpec.describe UsersController, type: :controller do
53
+ # # ...
54
+ # end
55
+ #
56
+ # The different available types are documented in the features, such as in
57
+ # https://relishapp.com/rspec/rspec-rails/docs
58
+ config.infer_spec_type_from_file_location!
59
+
60
+ # Filter lines from Rails gems in backtraces.
61
+ config.filter_rails_from_backtrace!
62
+ # arbitrary gems may also be filtered via:
63
+ # config.filter_gems_from_backtrace("gem name")
64
+ end
@@ -0,0 +1,52 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe 'people controller create', type: :request do
4
+ let(:request) { post '/people', params: params }
5
+
6
+ context 'with good params' do
7
+ let(:params) { { person: { first_name: 'Anthony', last_name: 'Guy' } } }
8
+
9
+ describe 'the effects of the request' do
10
+ subject { proc { request } }
11
+
12
+ it { should change(Person, :count).by(1) }
13
+ it { should change(Person.where(first_name: 'Anthony'), :count).by(1) }
14
+ end
15
+
16
+ describe 'the response' do
17
+ it 'is successful' do
18
+ request
19
+ expect(response).to be_successful
20
+ end
21
+
22
+ it 'returns the person' do
23
+ request
24
+ expect(JSON.parse(response.body)).to include('first_name' => 'Anthony')
25
+ end
26
+ end
27
+ end
28
+
29
+ context 'with bad params' do
30
+ let(:params) { { person: { first_name: '', last_name: '' } } }
31
+
32
+ describe 'the response' do
33
+ subject { request && response }
34
+
35
+ it { should_not be_successful }
36
+ it { should_not be_server_error }
37
+ it { should be_unprocessable }
38
+ end
39
+
40
+ describe 'the act of requesting' do
41
+ subject { proc { request } }
42
+
43
+ it { should_not change(Person, :count) }
44
+ end
45
+
46
+ describe 'the response body' do
47
+ subject { request && response && JSON.parse(response.body) }
48
+
49
+ it { should have_key('first_name') }
50
+ end
51
+ end
52
+ end