diff --git a/hw1/fibonacci.rb b/hw1/fibonacci.rb index e69de29..5e03eaa 100644 --- a/hw1/fibonacci.rb +++ b/hw1/fibonacci.rb @@ -0,0 +1,26 @@ +# Provides iteration over fibonacci sequence within a given size of elements +class Fibonacci + include Enumerable + + private + + attr_reader :quantity + + public + + def initialize(quantity = 1) + @quantity = quantity.to_i + raise 'Pass at least one number to iterate over!' if @quantity < 1 + end + + def each + previous = 1 + two_steps_before = 1 + quantity.times do |index| + current = index < 2 ? 1 : (previous + two_steps_before) + two_steps_before = previous + previous = current + yield(current) + end + end +end diff --git a/hw2/Gemfile b/hw2/Gemfile index f8bf844..745b639 100644 --- a/hw2/Gemfile +++ b/hw2/Gemfile @@ -1,5 +1,6 @@ # frozen_string_literal: true -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rack" -gem "rspec" +gem 'rack' +gem 'rspec' +gem 'pry-byebug' diff --git a/hw2/Gemfile.lock b/hw2/Gemfile.lock index ec73d13..86a6c9c 100644 --- a/hw2/Gemfile.lock +++ b/hw2/Gemfile.lock @@ -1,7 +1,17 @@ GEM remote: https://rubygems.org/ specs: + byebug (9.0.6) + coderay (1.1.1) diff-lcs (1.2.5) + method_source (0.8.2) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.4.0) + byebug (~> 9.0) + pry (~> 0.10) rack (2.0.1) rspec (3.5.0) rspec-core (~> 3.5.0) @@ -16,11 +26,13 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) + slop (3.6.0) PLATFORMS ruby DEPENDENCIES + pry-byebug rack rspec diff --git a/hw2/app/app.rb b/hw2/app/app.rb index 2be87a8..694ee4b 100644 --- a/hw2/app/app.rb +++ b/hw2/app/app.rb @@ -1,4 +1,4 @@ -Application = Router.new do +Application = Router::Base.new do get '/test', ->(env) { [200, {}, ['get test']] } post '/test', ->(env) { [200, {}, ['post test']] } end diff --git a/hw2/lib/router.rb b/hw2/lib/router.rb deleted file mode 100644 index eb6e53d..0000000 --- a/hw2/lib/router.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Router - def call(env) - @routes[env['REQUEST_METHOD']][env['REQUEST_PATH']].call(env) - end - -private - - def initialize(&block) - @routes = {} - instance_exec(&block) - end - - def get(path, rack_app) - match('GET', path, rack_app) - end - - def post(path, rack_app) - match('POST', path, rack_app) - end - - def match(http_method, path, rack_app) - @routes[http_method] ||= {} - @routes[http_method][path] = rack_app - end -end diff --git a/hw2/lib/router/base.rb b/hw2/lib/router/base.rb new file mode 100644 index 0000000..82299c8 --- /dev/null +++ b/hw2/lib/router/base.rb @@ -0,0 +1,50 @@ +# rubocop:disable Style/Documentation +module Router + class Base + def initialize(&block) + @routes = {} + instance_exec(&block) + end + + def call(env) + current(env)[:rack_app].call(env).tap do |rack_app| + merge_current_params(env, rack_app) + end + end + + private + + %i(get post put patch update delete).each do |http_method| + define_method http_method do |path, rack_app| + match(http_method.to_s.upcase, path, rack_app) + end + end + + def match(http_method, path, rack_app) + @routes[http_method] ||= {} + @routes[http_method][root_path path] = rack_app + end + + def current(env) + @routes[env['REQUEST_METHOD']].each do |path, rack_app| + path = path.split('/') + current = root_path(env['REQUEST_PATH']).split('/') + next unless path[1] == current[1] + return { path: path, rack_app: rack_app } + end + + { rack_app: ->(_env) { [404, {}, ['Ooops! We have not found:(']] } } + end + + def root_path(path) + path.start_with?('/') ? path : path.prepend('/') + end + + def merge_current_params(env, rack_app) + param = env['REQUEST_PATH'].split('/')[2] + return unless param + name = current(env)[:path][1..2].join('_').delete(':').upcase + rack_app[1] = rack_app[1].merge(name => param) + end + end +end diff --git a/hw2/main.rb b/hw2/main.rb index acfd4b3..bc9ba83 100644 --- a/hw2/main.rb +++ b/hw2/main.rb @@ -1,2 +1,2 @@ -require './lib/router' +require './lib/router/base' require './app/app' diff --git a/hw2/spec/lib/router_spec.rb b/hw2/spec/lib/router_spec.rb index e53a275..4066dc2 100644 --- a/hw2/spec/lib/router_spec.rb +++ b/hw2/spec/lib/router_spec.rb @@ -1,34 +1,89 @@ -RSpec.describe Router do +# rubocop:disable Metrics/BlockLength +RSpec.describe Router::Base do subject do - Router.new do - get '/test', ->(env) { [200, {}, ['get test']] } - post '/test', ->(env) { [200, {}, ['post test']] } - - ## - # TODO: router should match path by pattern like - # Pattern: /posts/:name - # Paths: - # /post/about_ruby - # /post/43 - # Cover this with tests. - # - get '/post/:name', ->(env) { [200, {}, ['post show page']] } + Router::Base.new do + get '/test', ->(_env) { [200, {}, ['get test']] } + post '/test', ->(_env) { [200, {}, ['post test']] } + get '/posts/:name', ->(_env) { [200, {}, ['post show page']] } + # get '/posts/:name/comments/:content', + # ->(_env) { [200, {}, ['deep comment show page']] } end end - context 'when request is GET' do - let(:env) { { 'REQUEST_PATH' => '/test', 'REQUEST_METHOD' => 'GET'} } + context 'under GET' do + let(:env) { { 'REQUEST_PATH' => '/test', 'REQUEST_METHOD' => 'GET' } } it 'matches request' do expect(subject.call(env)).to eq [200, {}, ['get test']] end end - context 'when request is POST' do - let(:env) { { 'REQUEST_PATH' => '/test', 'REQUEST_METHOD' => 'POST'} } + context 'under POST' do + let(:env) { { 'REQUEST_PATH' => '/test', 'REQUEST_METHOD' => 'POST' } } it 'matches request' do expect(subject.call(env)).to eq [200, {}, ['post test']] end end + + context 'under unspecified route' do + let(:envs) do + [ + { 'REQUEST_PATH' => '/missing', 'REQUEST_METHOD' => 'GET' }, + { 'REQUEST_PATH' => '/missing', 'REQUEST_METHOD' => 'POST' } + ] + end + + it 'renders 404' do + envs.each do |env| + expect(subject.call(env)) + .to eq [404, {}, ['Ooops! We have not found:(']] + end + end + end + + context 'under route with parameters' do + context 'with one parameter' do + let(:envs) do + [ + { 'REQUEST_PATH' => '/posts/about_ruby', 'REQUEST_METHOD' => 'GET' }, + { 'REQUEST_PATH' => '/posts/43', 'REQUEST_METHOD' => 'GET' }, + { 'REQUEST_PATH' => 'posts/43', 'REQUEST_METHOD' => 'GET' } + ] + end + + it 'will recognize it' do + envs.each do |env| + expect(subject.call(env)[0]).to eq 200 + expect(subject.call(env)[2]).to eq ['post show page'] + end + end + + it 'will return passed parameter' do + envs.each do |env| + expect(%w(about_ruby 43)) + .to include(subject.call(env)[1]['POSTS_NAME']) + end + end + end + + # context 'with nested parameters' do + # let(:envs) do + # [ + # { 'REQUEST_PATH' => '/posts/about_ruby', 'REQUEST_METHOD' => 'GET' }, + # { 'REQUEST_PATH' => '/posts/43', 'REQUEST_METHOD' => 'GET' }, + # { 'REQUEST_PATH' => 'posts/43', 'REQUEST_METHOD' => 'GET' } + # ] + # end + # + # it 'will recognize it' do + # envs.each do |env| + # expect(subject.call(env)[0]).to eq 200 + # expect(subject.call(env)[2]).to eq ['post show page'] + # end + # end + # + # it 'will return passed parameters for nested route' + # end + end end diff --git a/hw2/spec/spec_helper.rb b/hw2/spec/spec_helper.rb index ef0fca6..6874412 100644 --- a/hw2/spec/spec_helper.rb +++ b/hw2/spec/spec_helper.rb @@ -1,4 +1,5 @@ -require './lib/router' +# rubocop:disable all +require './main' RSpec.configure do |config| config.expect_with :rspec do |expectations|