Skip to content

Commit

Permalink
Start work on bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
aldesantis committed Dec 30, 2017
1 parent cc71cc5 commit d845293
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 156 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -245,4 +245,4 @@ The gem is available as open source under the terms of the [MIT License](http://
- [ ] Implement operation hooks
- [ ] Pass `Rack::Request` object from Rails to operations
- [ ] Implement hooks
- [ ] Create repository-request binding class to cache rolled/pending migrations (?)
- [ ] Create repository-request bind class to cache rolled/pending migrations (?)
1 change: 1 addition & 0 deletions lib/pragma/migration.rb
Expand Up @@ -7,6 +7,7 @@
require 'pragma/migration/repository'
require 'pragma/migration/version'
require 'pragma/migration/runner'
require 'pragma/migration/bind'
require 'pragma/migration/middleware'
require 'pragma/migration/version_number'

Expand Down
66 changes: 66 additions & 0 deletions lib/pragma/migration/bind.rb
@@ -0,0 +1,66 @@
module Pragma
module Migration
class Bind
attr_reader :repository, :request

def initialize(repository:, request:)
@repository = repository
@request = request
end

def pending_migrations
allocate_migrations unless @pending_migrations
@pending_migrations
end

def rolled_migrations
allocate_migrations unless @rolled_migrations
@rolled_migrations
end

def applying_migrations
compute_applying_migrations unless @applying_migrations
@applying_migrations
end

def migration_pending?(migration)
pending_migrations.include?(migration)
end

def migration_rolled?(migration)
rolled_migrations.include?(migration)
end

def migration_applies?(migration)
applying_migrations.include?(migration)
end

def user_version
@user_version ||= repository.user_version_from(request)
end

private

def allocate_migrations
@pending_migrations = []
@rolled_migrations = []

repository.sorted_versions.each do |version|
if version > user_version
@pending_migrations += version.migrations
else
@rolled_migrations += version.migrations
end
end
end

def compute_applying_migrations
@applying_migrations = []

pending_migrations.each do |migration|
@applying_migrations << migration if migration.applies_to?(request)
end
end
end
end
end
7 changes: 4 additions & 3 deletions lib/pragma/migration/middleware.rb
Expand Up @@ -6,16 +6,17 @@ class Middleware
def initialize(app, repository:)
@app = app
@repository = repository
@runner = Runner.new(@repository)
end

def call(env)
original_request = Rack::Request.new(env)
migrated_request = @runner.run_upwards(original_request)
runner = Runner.new(Bind.new(repository: @repository, request: original_request))

migrated_request = runner.run_upwards

status, headers, body = @app.call(migrated_request.env)
original_response = Rack::Response.new(body, status, headers)
migrated_response = @runner.run_downwards(original_request, original_response)
migrated_response = runner.run_downwards(original_response)

[migrated_response.status, migrated_response.headers, migrated_response.body]
end
Expand Down
57 changes: 8 additions & 49 deletions lib/pragma/migration/repository.rb
Expand Up @@ -8,60 +8,13 @@ def sorted_versions
versions.dup
end

def user_version_proc
@user_version_proc ||= lambda do |request|
request.get_header('X-Api-Version')
end
end

def migrations_since(request_or_version)
user_version = if request_or_version.is_a?(Rack::Request)
user_version_from(request_or_version)
else
request_or_version
end

@versions.select { |version| version > user_version }.flat_map(&:migrations)
end

def migrations_for(request)
migrations_since(request).select do |migration|
migration.applies_to?(request)
end
end

def migration_pending?(migration, request: nil, user_version: nil)
unless request || user_version
fail ArgumentError, 'You must pass one of :request or :user_version'
end

user_version ||= user_version_from(request)

@versions.any? do |version|
version > user_version && version.migration?(migration)
end
end

def migration_rolled?(migration, request: nil, user_version: nil)
unless request || user_version
fail ArgumentError, 'You must pass one of :request or :user_version'
end

user_version ||= user_version_from(request)

@versions.each do |version|
break false if version > user_version
break true if version.migration?(migration)
end
end

protected

def user_version_from(request)
user_version = user_version_proc.call(request)
sorted_versions.include?(user_version) ? user_version : sorted_versions.last
end

protected

def version(number, migrations = [])
versions << Version.new(number, migrations)
sort_versions
Expand All @@ -71,6 +24,12 @@ def determine_version_with(&block)
@user_version_proc = block
end

def user_version_proc
@user_version_proc ||= lambda do |request|
request.get_header('X-Api-Version')
end
end

private

def versions
Expand Down
34 changes: 23 additions & 11 deletions lib/pragma/migration/runner.rb
Expand Up @@ -3,26 +3,38 @@
module Pragma
module Migration
class Runner
attr_reader :repository
attr_reader :bind

def initialize(repository)
@repository = repository
def initialize(bind)
@bind = bind
end

def run_upwards(request)
repository.migrations_for(request).each do |migration|
request = migration.up(request)
def request
bind.request
end

def repository
bind.repository
end

def run_upwards
result = request

bind.applying_migrations.each do |migration|
result = migration.up(result)
end

request
result
end

def run_downwards(request, response)
repository.migrations_for(request).reverse.each do |migration|
response = migration.down(request, response)
def run_downwards(response)
result = response

bind.applying_migrations.reverse.each do |migration|
result = migration.down(request, result)
end

response
result
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions lib/pragma/migration/version.rb
Expand Up @@ -22,6 +22,16 @@ def ==(other)
self.<=>(other).zero?
end

def >=(other)
cmp = self.<=>(other)
cmp == 1 || cmp.zero?
end

def <=(other)
cmp = self.<=>(other)
cmp == -1 || cmp.zero?
end

def <=>(other)
other_number = other.is_a?(self.class) ? other.number : other
Gem::Version.new(number) <=> Gem::Version.new(other_number)
Expand Down
28 changes: 28 additions & 0 deletions spec/pragma/migration/bind_spec.rb
@@ -0,0 +1,28 @@
RSpec.describe Pragma::Migration::Bind do
subject { described_class.new(repository: repository, request: request) }

let(:migration1) { Class.new(Pragma::Migration::Base) }
let(:migration2) { Class.new(Pragma::Migration::Base) }
let(:migration3) { Class.new(Pragma::Migration::Base) }

let(:repository) do
Class.new(Pragma::Migration::Repository).tap do |repo|
repo.send :version, '2017-12-24'
repo.send :version, '2017-12-25', [migration1]
repo.send :version, '2017-12-26', [migration2]
repo.send :version, '2017-12-27', [migration3]
end
end

describe '.pending_migrations' do
it 'returns the migrations that must be executed'
end

describe '.rolled_migrations' do
it 'returns the migrations that have been executed'
end

describe '.applying_migrations' do
it 'returns the pending migrations that apply to this request'
end
end
6 changes: 4 additions & 2 deletions spec/pragma/migration/middleware_spec.rb
Expand Up @@ -37,7 +37,7 @@ def down
end

remove_id_from_category = Class.new(Pragma::Migration::Base) do
apply_to '/api/v1/posts/*'
apply_to '/api/v1/articles/*'

def up
request.update_param('category', request.delete_param('category_id'))
Expand Down Expand Up @@ -70,7 +70,9 @@ def down
.with(a_hash_including(
'rack.request.query_hash' => {
'author' => 'test_author_id',
'category' => 'test_category_id'
},
'rack.request.form_hash' => {
'category_id' => 'test_category_id',
}
))
.once
Expand Down
90 changes: 0 additions & 90 deletions spec/pragma/migration/repository_spec.rb
Expand Up @@ -27,94 +27,4 @@
expect(repository.sorted_versions.first.migrations).to eq([migration])
end
end

describe '.migrations_since' do
let(:migration1) { Class.new(Pragma::Migration::Base) }
let(:migration2) { Class.new(Pragma::Migration::Base) }
let(:migration3) { Class.new(Pragma::Migration::Base) }

before do
repository.send :version, '2017-12-24'
repository.send :version, '2017-12-25', [migration1]
repository.send :version, '2017-12-26', [migration2]
repository.send :version, '2017-12-27', [migration3]
end

it 'collects all the changes since the specified version' do
expect(repository.migrations_since('2017-12-25')).to eq([migration2, migration3])
end
end

describe '.migration_pending?' do
let(:migration1) { Class.new(Pragma::Migration::Base) }
let(:migration2) { Class.new(Pragma::Migration::Base) }

before do
repository.send :version, '2017-12-24'
repository.send :version, '2017-12-25', [migration1]
repository.send :version, '2017-12-26', [migration2]
end

it 'returns false when the migration is not pending' do
expect(repository).not_to be_migration_pending(migration1, user_version: '2017-12-25')
end

it 'returns true when the migration is pending' do
expect(repository).to be_migration_pending(migration2, user_version: '2017-12-25')
end
end

describe '.migration_rolled?' do
let(:migration1) { Class.new(Pragma::Migration::Base) }
let(:migration2) { Class.new(Pragma::Migration::Base) }

before do
repository.send :version, '2017-12-24'
repository.send :version, '2017-12-25', [migration1]
repository.send :version, '2017-12-26', [migration2]
end

it 'returns false when the migration has not been rolled' do
expect(repository).not_to be_migration_rolled(migration2, user_version: '2017-12-25')
end

it 'returns true when the migration has been rolled' do
expect(repository).to be_migration_rolled(migration1, user_version: '2017-12-25')
end
end

describe '.migrations_for' do
let(:migration1) do
Class.new(Pragma::Migration::Base) do
apply_to '/api/v1/articles/*'
end
end

let(:migration2) do
Class.new(Pragma::Migration::Base) do
apply_to '/api/v1/articles/*'
end
end

let(:migration3) do
Class.new(Pragma::Migration::Base) do
apply_to '/api/v1/posts/*'
end
end

before do
repository.send :version, '2017-12-24'
repository.send :version, '2017-12-25', [migration1]
repository.send :version, '2017-12-26', [migration2]
repository.send :version, '2017-12-27', [migration3]
end

it 'returns the migrations applying to the current request' do
env = Rack::MockRequest.env_for('/api/v1/articles/1', method: :patch).merge(
'X-Api-Version' => '2017-12-25'
)

expect(repository.migrations_for(Rack::Request.new(env))).to eq([migration2])
end
end
end

0 comments on commit d845293

Please sign in to comment.