Skip to content

Commit 792a922

Browse files
committed
Remove bad subdomain configuration
1 parent 86dd110 commit 792a922

File tree

1 file changed

+4
-167
lines changed

1 file changed

+4
-167
lines changed

rails6/en/chapter02-api.adoc

Lines changed: 4 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,7 @@ Up to this point we have not made anything crazy. What we want to generate is a
135135

136136
NOTE: Setting the API under a subdomain is a good practice because it allows the application to be adapted to a DNS level. But we will simplify things for now in our case.
137137

138-
You should be concerned about versioning your application from the beginning as this will give your API a *better structure*. So when changes occur on your API you can thus propose to developers to adapt to the new features while the old ones are depreciated.
139-
140-
//thus ?
138+
You should be concerned about versioning your application from the beginning as this will give your API a *better structure*. So when changes occur on your API you can thus propose to developers to adapt to the new features while the old ones are depreciated.
141139

142140
[source,ruby]
143141
.config/routes.rb
@@ -197,14 +195,14 @@ $ git merge chapter02
197195

198196
== Api versioning
199197

200-
At this point we should have a nice routes mapping using a subdomain for name spacing the requests. Your _routes.rb_ file should look like this:
198+
At this point we should have a nice routes mapping using a namespace. Your _routes.rb_ file should look like this:
201199

202200
[source,ruby]
203201
.config/routes.rb
204202
----
205203
Rails.application.routes.draw do
206204
# Api definition
207-
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
205+
namespace :api, defaults: { format: :json }, path: '/' do
208206
# We are going to list our resources here
209207
end
210208
end
@@ -226,7 +224,7 @@ This way we can scope our api into different versions very easily, now we just n
226224
----
227225
Rails.application.routes.draw do
228226
# Api definition
229-
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
227+
namespace :api, defaults: { format: :json }, path: '/' do
230228
scope module: :v1 do
231229
# We are going to list our resources here
232230
end
@@ -236,167 +234,6 @@ end
236234

237235
By this point the API is now scoped via the URL. For example with the current configuration an end point for retrieving a product would be like: http://localhost:3000/v1/products/1.
238236

239-
=== Improving the versioning
240-
241-
So far we have the API versioned scoped via the URL, but something doesn’t feel quite right, isn’t it? From my point of view the developer should not be aware of the version using it, by default they should be using the last version of your endpoints, but how do we accomplish this?.
242-
243-
Well first of all, we need to improve the API version access through http://en.wikipedia.org/wiki/List_of_HTTP_header_fields[HTTP Headers]. This has two benefits:
244-
245-
* Removes the version from the URL
246-
* The API description is handle through request headers
247-
248-
.Most commons HTTP headers fields
249-
****
250-
HTTP header fields are components of the message header of requests and responses in the Hypertext Transfer Protocol (HTTP). They define an operating parameters of an HTTP transaction. A common list of used headers is presented below:
251-
252-
* *Accept*: Content-Types acceptables for the response. Example: `Accept: text/plain`
253-
* *Authorization*: Authentication credentials for HTTP authentication. Example: `Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==`
254-
* *Content-Type*: The MIME type of the body of the request (used with POST and PUT requests). Example: `Content-Type: application/x-www-form-urlencoded`
255-
* *Origin*: Initiates a request for cross-origin resource sharing (asks server for an `Access-Control-Allow-Origin' response header). Example: `Origin: http://www.example-social-network.com`
256-
* *User-Agent*: The user agent string of the user agent. Example: `User-Agent: Mozilla/5.0`
257-
258-
It is important you feel comfortable with this ones and understand them.
259-
260-
****
261-
262-
// In Rails it's very easy to add this type versioning through an _Accept_ header. We will create a class under the `lib` directory of your rails app, and remember we are doing http://en.wikipedia.org/wiki/Test-driven_development[TDD] so first things first.
263-
264-
// First we need to add our testing suite, which in our case is going to be http://rspec.info/[Rspe]:
265-
266-
// [source,ruby]
267-
// .Gemfile
268-
// ----
269-
// group :test do
270-
// gem 'rspec-rails', '~> 3.8'
271-
// gem 'factory_bot_rails', '~> 4.9'
272-
// gem 'ffaker', '~> 2.10'
273-
// end
274-
// ----
275-
276-
// Then we run the bundle command to install the gems
277-
278-
// [source,bash]
279-
// ----
280-
// $ bundle install
281-
// ----
282-
283-
// Finally we install the `rspec` and add some configuration to prevent views and helpers tests from being generated:
284-
285-
// [source,bash]
286-
// ----
287-
// $ rails generate rspec:install
288-
// ----
289-
290-
// [source,ruby]
291-
// .config/application.rb
292-
// ----
293-
// # ...
294-
// module MarketPlaceApi
295-
// class Application < Rails::Application
296-
// # Initialize configuration defaults for originally generated Rails version.
297-
// config.load_defaults 5.2
298-
299-
// config.generators do |g|
300-
// g.test_framework :rspec, fixture: true
301-
// g.fixture_replacement :factory_bot, dir: 'spec/factories'
302-
// g.view_specs false
303-
// g.helper_specs false
304-
// g.stylesheets = false
305-
// g.javascripts = false
306-
// g.helper = false
307-
// end
308-
309-
// config.autoload_paths += %W(\#{config.root}/lib)
310-
311-
// # Don't generate system test files.
312-
// config.generators.system_tests = nil
313-
// end
314-
// end
315-
// ----
316-
317-
// If everything went well it is now time to add a `spec` directory under `lib` and add the `api_constraints_test.rb`:
318-
319-
// [source,bash]
320-
// ----
321-
// $ mkdir lib/spec
322-
// $ touch lib/spec/api_constraints_test.rb
323-
// ----
324-
325-
//Then we add a bunch of specs describing our class:
326-
327-
// [source,ruby]
328-
// .lib/spec/api_constraints_test.rb
329-
// ----
330-
// require 'spec_helper'
331-
// require './lib/api_constraints'
332-
333-
// describe ApiConstraints do
334-
// let(:api_constraints_v1) { ApiConstraints.new(version: 1) }
335-
// let(:api_constraints_v2) { ApiConstraints.new(version: 2, default: true) }
336-
337-
// describe 'matches?' do
338-
// it "returns true when the version matches the 'Accept' header" do
339-
// request = double(host: 'api.marketplace.dev',
340-
// headers: { 'Accept' => 'application/vnd.marketplace.v1' })
341-
// expect(api_constraints_v1.matches?(request)).to be_truthy
342-
// end
343-
344-
// it "returns the default version when 'default' option is specified" do
345-
// request = double(host: 'api.marketplace.dev')
346-
// expect(api_constraints_v2.matches?(request)).to be_truthy
347-
// end
348-
// end
349-
// end
350-
// ----
351-
352-
// Let me walk you through the code. We are initializing the class with an https://ruby-doc.org/core-2.4.0/Hash.html[`Hash`] option. https://ruby-doc.org/core-2.4.0/Hash.html[`Hash`] option will contain the version of the API and a default value for handling the default version. We provide a `matches?` method which the router will trigger for the constraint to see if the default version is required or the `Accept` header matches the given string.
353-
354-
// The implementation looks likes this
355-
356-
// [source,ruby]
357-
// .lib/api_constraints.rb
358-
// ----
359-
// class ApiConstraints
360-
// def initialize(options)
361-
// @version = options[:version]
362-
// @default = options[:default]
363-
// end
364-
365-
// def matches?(req)
366-
// @default || req.headers['Accept'].include?("application/vnd.marketplace.v#{@version}")
367-
// end
368-
// end
369-
// ----
370-
371-
// As you imagine we need to add the class to our `routes.rb` file and set it as a constraint scope option.
372-
373-
// [source,ruby]
374-
// .config/routes.rb
375-
// ----
376-
// # ...
377-
// Rails.application.routes.draw do
378-
// # Api definition
379-
// namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
380-
// scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
381-
// # We are going to list our resources here
382-
// end
383-
// end
384-
// end
385-
// ----
386-
387-
// The configuration above now handles versioning through headers, and for now the version 1 is the default one, so every request will be redirected to that version, no matter if the header with the version is present or not.
388-
389-
// Before we say goodbye, let’s run our first tests and make sure everything is nice and green:
390-
391-
// [source,bash]
392-
// ----
393-
// $ bundle exec rspec lib/spec/api_constraints_test.rb
394-
// ..
395-
396-
// Finished in 0.00294 seconds (files took 0.06292 seconds to load)
397-
// 2 examples, 0 failures
398-
// ----
399-
400237
== Conclusion
401238

402239
It’s been a long way, I know, but you made it, don’t give up this is just our small scaffolding for something big, so keep it up. In the meantime and if you feel curious there are some gems that handle this kind of configuration:

0 commit comments

Comments
 (0)