diff --git a/.travis.yml b/.travis.yml index 2d528b0..f3f835d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ +sudo: false language: ruby rvm: - - 2.3.1 + - 2.3.0 script: - bash check.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0a534c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# kottans_2016_homeworks + +gem https://github.com/lysenko-sergei-developer/lysenko_kottans_rack +travis https://travis-ci.org/lysenko-sergei-developer/lysenko_kottans_rack diff --git a/hw1/fibonacci.rb b/hw1/fibonacci.rb index e69de29..93e7486 100644 --- a/hw1/fibonacci.rb +++ b/hw1/fibonacci.rb @@ -0,0 +1,18 @@ +class Fibonacci + include Enumerable + + def initialize( n ) + @n = n + end + + def each + x, y = 0, 1 + + @n.times do + z = (x + y) + x = y + y = z + yield x + end + end +end \ No newline at end of file diff --git a/hw2/Gemfile b/hw2/Gemfile index f8bf844..b9b145b 100644 --- a/hw2/Gemfile +++ b/hw2/Gemfile @@ -3,3 +3,4 @@ source "https://rubygems.org" gem "rack" gem "rspec" +gem "oj" \ No newline at end of file diff --git a/hw2/Gemfile.lock b/hw2/Gemfile.lock index ec73d13..3643f22 100644 --- a/hw2/Gemfile.lock +++ b/hw2/Gemfile.lock @@ -2,6 +2,7 @@ GEM remote: https://rubygems.org/ specs: diff-lcs (1.2.5) + oj (2.17.5) rack (2.0.1) rspec (3.5.0) rspec-core (~> 3.5.0) @@ -21,8 +22,9 @@ PLATFORMS ruby DEPENDENCIES + oj rack rspec BUNDLED WITH - 1.13.5 + 1.13.6 diff --git a/hw2/app/app.rb b/hw2/app/app.rb index 2be87a8..7c74a80 100644 --- a/hw2/app/app.rb +++ b/hw2/app/app.rb @@ -1,4 +1,23 @@ -Application = Router.new do - get '/test', ->(env) { [200, {}, ['get test']] } - post '/test', ->(env) { [200, {}, ['post test']] } +class TestController < Controller #inherit Controller class + def show # method show for return params + response(:json, params) + end + + def test # method test for return request method + response(:text, "Request method: #{request.request_method}") # params = "Request method: #{request.request_method}", + end +end + +Application = Router.new do # create new instance of Router + + # get '/test', ->(env) { [200, {}, ['get test']] } + # post '/test', ->(env) { [200, {}, ['post test']] } + + get '/post/:name/:other_one', 'test#show' + get '/test', 'test#test' # "#{request.request_method} = get" + + #get '/users/:id', ->(env) { [200, {}, ['post /users/:id']] } + # get '/users/:id/comments/:comment/rating/:rating', ->(env) { [200, {}, ['post with users comment rating']] } + # get '/users/articles/:title', ->(env) { [200, {}, ['post with user articles title']] } + end diff --git a/hw2/lib/controller.rb b/hw2/lib/controller.rb new file mode 100644 index 0000000..4849bfb --- /dev/null +++ b/hw2/lib/controller.rb @@ -0,0 +1,36 @@ +class Controller + RESPONSE_TYPES = { # mini cheat, for work with response + text: ['text/plain', ->(c) { c.to_s }], # key where stored text params data + json: ['application/json', ->(c) { Oj.dump(c) }] # key where stored json + }.freeze # freeze this constant, for avoid random changing + + def call(env) # env standart Rack params + @env = env # writed env into instance @env + @request = Rack::Request.new(env) # writed new rack request into instance @request + @request.params.merge!(env['router.params'] || {}) # merge with router.params if it exist + send(@action_name) # send @action_name + + [200, @response_headers, [@response_body]] # return array with route + end + + def self.action(action_name) # call action method for self object, action_name = call method name + proc { |env| new(action_name).call(env) } # proc create new action and call it + end + +private + attr_reader :request # create getter for request + + def initialize(action_name) # init with, call method name + @action_name = action_name # writed action_name into instance @action_name + end + + def params # params return request params + request.params + end + + def response(type, content) #response in header and body + @response_headers ||= {} # if @response_headers empty assign hash + @response_headers.merge!('Content-Type' => RESPONSE_TYPES[type][0]) # merge @response_headers with 'Content-Type' => RESPONSE_TYPES[text]['some text'] + @response_body = RESPONSE_TYPES[type][1].call(content) # assign @response_body to RESPONSE_TYPES[json][some json] and call content arguments + end +end diff --git a/hw2/lib/router.rb b/hw2/lib/router.rb index eb6e53d..b534818 100644 --- a/hw2/lib/router.rb +++ b/hw2/lib/router.rb @@ -1,25 +1,70 @@ class Router - def call(env) - @routes[env['REQUEST_METHOD']][env['REQUEST_PATH']].call(env) + def call(env) # call method with env params + find_route(env).call(env) # call env end private - def initialize(&block) - @routes = {} - instance_exec(&block) + def initialize(&block) # initialize of code blok + @routes = [] # set to null @routes + instance_exec(&block) #call &block end - def get(path, rack_app) + def find_route(env) # check if route exist + @routes.each do |route| # in @routes all route + # check if requst method of call and requst method of route matche + # and requst path of call math with route path + if env['REQUEST_METHOD'] == route[:method] && env['REQUEST_PATH'] =~ route[:regexp] + # if all matches, assing to router.params extract params + env['router.params'] = extract_params(route[:pattern], env['REQUEST_PATH']) + return route[:app] # return applocation + end + end + + return ->(_env) { [404, {}, ['not found']] } # if not exist return -> 404 + end + + def get(path, rack_app) # get request match('GET', path, rack_app) end - def post(path, rack_app) + def post(path, rack_app) # post request match('POST', path, rack_app) end - def match(http_method, path, rack_app) - @routes[http_method] ||= {} - @routes[http_method][path] = rack_app + def match(http_method, path, rack_app) # match + rack_app = get_controller_action(rack_app) if rack_app.is_a?(String) + @routes << { pattern: path, app: rack_app, regexp: path_to_regexp(path), method: http_method } + end + + def get_controller_action(str) + controller_name, action_name = str.split('#') # tests#show => ['tests', 'show'] + controller_name = to_upper_camel_case(controller_name)# ['tests', 'show'] => ['TestsController', 'show'] + Kernel.const_get(controller_name).send(:action, action_name) + end + + def to_upper_camel_case(str) # up + str # 'public_pages/tests' => PublicPages::TestsController + .split('/') # ['public_pages', 'test'] + .map { |part| part.split('_').map(&:capitalize).join } # ['PublicPages', 'Test'] + .join('::') + 'Controller' + end + + + # /post/:name + def path_to_regexp(path) # return processing path + Regexp.new('\A' + path.gsub(/:[\w-]+/, '[\w-]+') + '\Z') + end + + # /post/:name + # /post/test_one + # { name: 'test_one' } + def extract_params(pattern, path) + pattern + .split('/') # ['post', ':name'] + .zip(path.split('/')) # [['post', 'post'],[':name', 'post']] + .reject { |e| e.first == e.last } # [[':name', 'post']] + .map { |e| [e.first[1..-1], e.last] } # [['name', 'post']] + .to_h # { name = > post } end end diff --git a/hw2/main.rb b/hw2/main.rb index acfd4b3..c7ac84b 100644 --- a/hw2/main.rb +++ b/hw2/main.rb @@ -1,2 +1,6 @@ +require 'rack' +require 'oj' + +require './lib/controller' require './lib/router' require './app/app' diff --git a/hw2/spec/lib/controller_spec.rb b/hw2/spec/lib/controller_spec.rb new file mode 100644 index 0000000..c47d329 --- /dev/null +++ b/hw2/spec/lib/controller_spec.rb @@ -0,0 +1,35 @@ +RSpec.describe Controller do + let(:controller) do + + Class.new(Controller) do + def text_action + response(:text, 'test text') + end + + def json_action + response(:json, params) + end + + def data_params + response(:json, some_key: params['some_info']) + end + end + end + + context 'shows request status' do + it 'for action with text type' do + expect(controller.action(:text_action).call(Rack::MockRequest.env_for('/test_in_text'))) + .to eq([200, { 'Content-Type' => 'text/plain' }, ['test text']]) + end + + it 'for action with json type' do + expect(controller.action(:json_action).call(Rack::MockRequest.env_for('/test_in_json'))) + .to eq([200, { 'Content-Type' => 'application/json' }, ["{}"]]) + end + end + + it 'shows readed param from address line to params hash' do + expect(controller.action(:data_params).call(Rack::MockRequest.env_for('/message?some_info=key'))) + .to eq([200, { 'Content-Type' => 'application/json' }, ["{\":some_key\":\"key\"}"]]) + end +end diff --git a/hw2/spec/lib/router_spec.rb b/hw2/spec/lib/router_spec.rb index e53a275..ba7e80d 100644 --- a/hw2/spec/lib/router_spec.rb +++ b/hw2/spec/lib/router_spec.rb @@ -4,15 +4,10 @@ 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']] } + get '/users/5', ->(env) { [200, {}, ['get /users/:id']] } + get '/users/5/comments/2/rating/4', ->(env) { [200, {}, ['get /users/:id/comments/:comment/rating/:rating']] } + get '/users/articles/14', ->(env) { [200, {}, ['get /users/articles/:title']]} + get '/', ->(env) { [404, {}, ['page not found']] } end end @@ -31,4 +26,37 @@ expect(subject.call(env)).to eq [200, {}, ['post test']] end end -end + + context 'router by pattern /users/:id' do + let(:env) { { 'REQUEST_PATH' => "/users/5", 'REQUEST_METHOD' => 'GET'} } + + it 'matches request' do + expect(subject.call(env)).to eq [200, {}, ['get /users/:id']] + end + end + + context 'router by pattern /users/:id/comments/:comment/rating/:rating' do + let(:env) { { 'REQUEST_PATH' => "/users/5/comments/2/rating/4", 'REQUEST_METHOD' => 'GET'} } + + it 'matches request' do + expect(subject.call(env)).to eq [200, {}, ['get /users/:id/comments/:comment/rating/:rating']] + end + end + + context 'router by pattern /users/articles/:title' do + let(:env) { { 'REQUEST_PATH' => "/users/articles/14", 'REQUEST_METHOD' => 'GET'} } + + it 'matches request' do + expect(subject.call(env)).to eq [200, {}, ['get /users/articles/:title']] + end + end + + context 'when path has parameter' do + let(:env) { { 'REQUEST_PATH' => "/", 'REQUEST_METHOD' => 'GET'} } + + it 'returns 404' do + expect(subject.call(env)).to eq [404, {}, ['page not found']] + end + end + +end \ No newline at end of file diff --git a/hw2/spec/spec_helper.rb b/hw2/spec/spec_helper.rb index ef0fca6..f35e583 100644 --- a/hw2/spec/spec_helper.rb +++ b/hw2/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'rack' +require 'oj' #optimized json +require './lib/controller' require './lib/router' RSpec.configure do |config| diff --git a/hw3 b/hw3 new file mode 160000 index 0000000..fd37b12 --- /dev/null +++ b/hw3 @@ -0,0 +1 @@ +Subproject commit fd37b12d9965848b4e73e37d28a2eaa3e60276bc