mini-train 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ >There's a lady who sures
2
+ >all that glitters is gold
3
+ >and she's buying a stairway to heaven -- Led Zeppelin
4
+
5
+ mini-train
6
+ =====
7
+
8
+ mini-train is an attempt to fork from a rack project(rack-golem) into
9
+ a very simple web mini-framework (thats the name mini-train)
10
+
11
+ Install with: (there are no gems yet, so don't try to install right now)
12
+
13
+ sudo gem install mini-train
14
+
15
+ You can use config.ru as a start up file as for any rack app based
16
+
17
+ require 'models' # Loads your models and all ORM stuff
18
+ require 'app' # This is the main file
19
+ require 'json' # Some help from other gems
20
+ use Rack::ContentLength
21
+ use Rack::Session::Cookies
22
+ run App
23
+
24
+ Now save this into app.rb
25
+
26
+ require 'minitrain'
27
+
28
+ class App
29
+ include Minitrain # Still no classes to inherit but don't worry just await!
30
+
31
+ before do
32
+ # Here you can do many things
33
+ # In order to help you here are some variables you can read and override:
34
+ # @r => the Rack::Request object
35
+ # @res => the Rack::Response object
36
+ # @action => Name of the public method that will handle the request
37
+ # @action_arguments => Arguments for the action (really?)
38
+ end
39
+
40
+ helpers do
41
+ #altough you can write many things in before block
42
+ #you always need helpers for your main app
43
+ def help(with)
44
+ "Helping the World!"
45
+ end
46
+ end
47
+
48
+ def index(*args)
49
+ # When no public method is found
50
+ # Of course you don't have to declare one and it is gonna use Controller#not_found instead
51
+ # Still can have arguments
52
+ @articles = Post.all
53
+ erb :index
54
+ end
55
+
56
+ def post(id=nil)
57
+ @post = Post[id]
58
+ if @post.nil?
59
+ not_found
60
+ else
61
+ erb :post
62
+ end
63
+ end
64
+
65
+ def best_restaurants_json
66
+ # mini-train replaces dots and dashes with underscores
67
+ # So you can trigger this handler by visiting /best-restaurant.json
68
+ json_response({
69
+ 'title' => 'Best restaurants in town',
70
+ 'list' => Restaurant.full_list
71
+ })
72
+
73
+ def say(listener='me', *words)
74
+ "Hey #{listener} I don't need ERB to tell you that #{words.join(' ')}"
75
+ end
76
+
77
+ def not_found(*args)
78
+ # This one is defined by mini-train but here we decided to override it
79
+ # Like :index this method receives the arguments in order to make something with it
80
+ Email.alert('Too many people are looking for porn here') if args.includes?("porn")
81
+ super(args)
82
+ end
83
+
84
+ def error(err, *args)
85
+ # Again this one is defined by mini-train and only shows up when RACK_ENV is not `nil` or `dev` or `development`
86
+ # Default only prints "ERROR"
87
+ # Here we're going to send the error message
88
+ # One would rarely show that to the end user but this is just a demo
89
+ err.message
90
+ end
91
+
92
+ after do
93
+ Spy.analyse.send_info_to([:government, :facebook, :google, :james_bond])
94
+ #this was a joke
95
+ end
96
+
97
+ end
98
+
99
+ You need to provide the class Post in your models.
100
+ Class Spy doesn't exist so you need to create it (he he, just kidding).
101
+
102
+ WHAT mini-train DOES NOT
103
+ ===================
104
+
105
+ - Support templates other than ERB (Slim, Haml and other tilt based templates has been added)
106
+ - Session/Cookies administration (Like for many things, use a middleware instead ex: Rack::Session::Cookies)
107
+ - Prepare the coffee (Emacs does but i had never used it, just geany)
108
+ - So many things, but what is a fork for...
data/lib/minitrain.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'rack'
2
+ require 'tilt'
3
+
4
+ module Minitrain
5
+
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend ClassMethods
9
+ include InstanceMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ attr_reader :before_block, :helpers_block, :after_block
15
+ def before(&block); @before_block = block; end
16
+ def helpers(&block); @helpers_block = block; end
17
+ def after(&block); @after_block = block; end
18
+ def dispatcher(&block); @dispatcher_block = block; end
19
+ def dispatcher_block
20
+ @dispatcher_block || proc{
21
+ @path_atoms = @r.path_info.split('/').find_all{|s| s!=''}
22
+ @action, *@action_arguments = @path_atoms
23
+ @action = @action.gsub(/[-.]/, '_') unless @action.nil?
24
+ unless public_methods.include?(@action)||(@action&&public_methods.include?(@action.to_sym))
25
+ if public_methods.include?('index')||public_methods.include?(:index) # For different RUBY_VERSION(s)
26
+ @action, @action_arguments = 'index', @path_atoms
27
+ else
28
+ @action, @action_arguments = 'not_found', @path_atoms
29
+ end
30
+ end
31
+ instance_eval(&self.class.before_block) unless self.class.before_block.nil?
32
+ instance_eval(&self.class.helpers_block) unless self.class.helpers_block.nil?
33
+ begin
34
+ @res.write(self.__send__(@action,*@action_arguments))
35
+ rescue ArgumentError => e
36
+ failed_method = e.backtrace[0][/`.*'$/][1..-2]
37
+ raise unless failed_method==@action
38
+ @res.write(self.__send__('not_found', @path_atoms))
39
+ end
40
+ instance_eval(&self.class.after_block) unless self.class.after_block.nil?
41
+ }
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+
47
+ DEV_ENV = [nil,'development','dev']
48
+
49
+ def initialize(app=nil); @app = app; end
50
+ def call(env); dup.call!(env); end
51
+
52
+ def call!(env)
53
+ catch(:response) {
54
+ @r = ::Rack::Request.new(env)
55
+ @res = ::Rack::Response.new
56
+ @session = env['rack.session'] || {}
57
+ begin
58
+ instance_eval(&self.class.dispatcher_block)
59
+ rescue => e
60
+ raise if DEV_ENV.include?(ENV['RACK_ENV'])
61
+ @res.write(self.__send__('error', e, @path_atoms))
62
+ end
63
+ @res.status==404&&!@app.nil? ? @app.call(env) : @res.finish
64
+ }
65
+ end
66
+
67
+ def not_found(*args)
68
+ @res.status = 404
69
+ @res.headers['X-Cascade']='pass'
70
+ "NOT FOUND: #{@r.path_info}"
71
+ end
72
+
73
+ def error(e, *args)
74
+ puts "\n", e.class, e.message, e.backtrace # Log the error anyway
75
+ @res.status = 500
76
+ "ERROR 500"
77
+ end
78
+
79
+ def tpl(template, extention)
80
+ key = (template.to_s + extention.gsub(/[.]/,"_")).to_sym
81
+ @@tilt_cache ||= {}
82
+ if @@tilt_cache.has_key?(key)
83
+ template_obj = @@tilt_cache[key]
84
+ else
85
+ views_location = @r.env['views'] || ::Dir.pwd+'/views/'
86
+ views_location << '/' unless views_location[-1]==?/
87
+ template_obj = Tilt.new("#{views_location}#{template}#{extention}")
88
+ @@tilt_cache.store(key,template_obj) if ENV['RACK_ENV']=='production'
89
+ end
90
+ @res['Content-Type'] = "text/html;charset=utf-8"
91
+ template_obj.render(self)
92
+ end
93
+
94
+ def erb(template)
95
+ tpl(template,'.erb')
96
+ end
97
+
98
+ def slim(template)
99
+ tpl(template,'.slim')
100
+ end
101
+
102
+ def haml(template)
103
+ tpl(template,'.haml')
104
+ end
105
+
106
+ def scss(template)
107
+ tpl(template,'.scss')
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'mini-train'
3
+ s.version = "0.1.0"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.summary = "An attempt to create a web mini-framework forked from an rack app (rack-golem)"
6
+ s.description = "This is a simplistic web framework"
7
+ s.files = `git ls-files`.split("\n").sort
8
+ s.require_path = './lib'
9
+ s.author = "Carlos Esquer"
10
+ s.email = "carlitos.esquer@gmail.com"
11
+ s.homepage = "http://rubymx.blogspot.com"
12
+ s.add_dependency(%q<tilt>, [">= 1.2.2"])
13
+ #s.add_development_dependency(%q<bacon>, "~> 1.1.0")
14
+ end
data/sample/app.rb ADDED
@@ -0,0 +1,104 @@
1
+ require 'minitrain'
2
+ require 'slim'
3
+ require 'yaml'
4
+ require 'json'
5
+ class App
6
+ include Minitrain # Still no classes to inherit but don't worry just await!
7
+
8
+ before do
9
+ # Here you can do many things
10
+ # In order to help you here are some variables you can read and override:
11
+ # @r => the Rack::Request object
12
+ # @res => the Rack::Response object
13
+ # @action => Name of the public method that will handle the request
14
+ # @action_arguments => Arguments for the action (really?)
15
+ end
16
+
17
+ helpers do
18
+ #altough you can write many things in before block
19
+ #we recommend you to write helpers here to give your app some structure
20
+ def simple
21
+ "<b>Siempre genera BOLD</b>"
22
+ end
23
+
24
+ def json_response(data=nil)
25
+ @res['Content-Type'] = "text/plain;charset=utf-8"
26
+ data.nil? ? "{}" : JSON.generate(data)
27
+ end
28
+
29
+ def yaml_response(data=nil)
30
+ @res['Content-Type'] = "text/plain;charset=utf-8"
31
+ data.to_yaml
32
+ end
33
+
34
+ end
35
+
36
+ def index(*args)
37
+ # When no public method is found
38
+ # Of course you don't have to declare one and it is gonna use Controller#not_found instead
39
+ # Still can have arguments
40
+ @articles = [{titulo: "hola mundo",contenido: "Este mundo material"},
41
+ {titulo: "bienvenidos", contenido: "Nunca habra paz..."},
42
+ {titulo: "el final", contenido: "como en los viejos tiempos"}
43
+ ]
44
+ simple + (slim :index)
45
+ #"<h1>Bienvenido</h1><br><h3>Hola mundo...</h3>"
46
+ end
47
+
48
+ def post(id=nil)
49
+ @post = Post[id]
50
+ if @post.nil?
51
+ not_found
52
+ else
53
+ erb :post
54
+ end
55
+ end
56
+
57
+ def best_restaurants_json
58
+ # mini-train replaces dots and dashes with underscores
59
+ # So you can trigger this handler by visiting /best-restaurants.json
60
+ json_response({
61
+ 'title' => 'Best restaurants in town',
62
+ 'list' => "{ nuevo: 'artículo', antiguo: 'años'}"
63
+ })
64
+ end
65
+
66
+ def best_restaurants_yaml
67
+ #you can generate YAML responses (next time we will use XML)
68
+ yaml_response({
69
+ 'title' => 'Best restaurants in town',
70
+ 'list' => "{ nuevo: 'artículo', antiguo: 'años'}"
71
+ })
72
+ end
73
+
74
+ def envy(*args)
75
+ @res['Content-Type'] = "text/html;charset=utf-8"
76
+ my_res = ""
77
+ my_res += "<p> Método: #{@r.request_method}</p>"
78
+ my_res += "<p> Metodo: #{@r.query_string}</p>"
79
+ end
80
+
81
+ def say(listener='me', *words)
82
+ "Hey #{listener} I don't need ERB to tell you that #{words.join(' ')}"
83
+ end
84
+
85
+ def not_found(*args)
86
+ # This one is defined by mini-train but here we decided to override it
87
+ # Like :index this method receives the arguments in order to make something with it
88
+ #Email.alert('Too many people are looking for porn here') if args.includes?("porn")
89
+ super(args)
90
+ end
91
+
92
+ def error(err, *args)
93
+ # Again this one is defined by mini-train and only shows up when RACK_ENV is not `nil` or `dev` or `development`
94
+ # Default only prints "ERROR"
95
+ # Here we're going to send the error message
96
+ # One would rarely show that to the end user but this is just a demo
97
+ err.message
98
+ end
99
+
100
+ after do
101
+ #Spy.analyse.send_info_to([:government, :facebook, :google, :james_bond])
102
+ end
103
+ end
104
+
data/sample/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ #require 'models' # Loads your models and all ORM stuff
2
+ require './app' # This is the main file
3
+ # Some help from other gems
4
+ use Rack::ContentLength
5
+ #use Rack::Session::Cookies
6
+ run App.new
@@ -0,0 +1,8 @@
1
+ <head>
2
+ <link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Play:400,700'/>
3
+ <link rel="stylesheet" type="text/css" href="/css/layout.css"/>
4
+ </head>
5
+ <h1>Algunos artículos (.erb)...<h1>
6
+ <% @articles.each do |art| %>
7
+ <h2> <%= art[:titulo] %> </h2>
8
+ <% end %>
@@ -0,0 +1,9 @@
1
+ %head
2
+ %link{:rel=>'stylesheet', :type=>'text/css', :href=>'http://fonts.googleapis.com/css?family=Play:400,700'}
3
+ %link{:rel=>"stylesheet", :type=>"text/css", :href=>"/css/layout.css"}
4
+ %body
5
+ %h1 Algunos artículos (.haml)...
6
+ %br
7
+ - @articles.each do |art|
8
+ %h2= art[:titulo]
9
+ %br
@@ -0,0 +1,11 @@
1
+ head
2
+ link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Play:400,700'
3
+ link rel="stylesheet" type="text/css" href="/css/layout.css"
4
+ body
5
+ h1 Algunos artículos (.slim)...
6
+ br
7
+ - @articles.each do |art|
8
+ h2= art[:titulo]
9
+ br
10
+ .content=art[:contenido]
11
+
@@ -0,0 +1,185 @@
1
+ require 'minitest/autorun'
2
+ require 'rack/lobster'
3
+ require 'minitrain'
4
+
5
+ # =========
6
+ # = Basic =
7
+ # =========
8
+
9
+ class Basic
10
+ include Minitrain
11
+ def no_arg; 'nothing'; end
12
+ def with_args(a,b); '%s+%s' % [a,b]; end
13
+ def splat_arg(*a); a.join('+'); end
14
+ def test_throw
15
+ throw :response, [200,{'Content-Type'=>'text/html'},['Growl']]
16
+ 'Grrr'
17
+ end
18
+ def best_restaurants_rss; '<xml>test</xml>'; end
19
+ private
20
+ def no_way; 'This is private'; end
21
+ end
22
+ BasicR = ::Rack::MockRequest.new(::Rack::Lint.new(Basic.new))
23
+ BasicLobsterR = ::Rack::MockRequest.new(::Rack::Lint.new(Basic.new(::Rack::Lobster.new)))
24
+
25
+ # ==========
26
+ # = Filter =
27
+ # ==========
28
+
29
+ class Filter
30
+ include Minitrain
31
+ before{@res.write @action=='not_found' ? @action_arguments.join('+') : 'before+'}
32
+ after{@res.write '+after'}
33
+ def wrapped; 'wrapped'; end
34
+ end
35
+ FilterR = ::Rack::MockRequest.new(::Rack::Lint.new(Filter.new))
36
+
37
+ # ===========
38
+ # = Indexed =
39
+ # ===========
40
+
41
+ class Indexed
42
+ include Minitrain
43
+ before{ @res.write("action=#{@action} args=#{@action_arguments.join(',')} ") if @r['switch']=='true' }
44
+ def index(*a); a.join('+'); end
45
+ def exist(*a); a.join('+'); end
46
+ end
47
+ IndexedR = ::Rack::MockRequest.new(::Rack::Lint.new(Indexed.new))
48
+
49
+ # ==================
50
+ # = Simply indexed =
51
+ # ==================
52
+
53
+ class SimplyIndexed
54
+ include Minitrain
55
+ def index; 'index'; end
56
+ def will_fail; please_fail; end
57
+ private
58
+ def please_fail(num); 'ArgumentError baby'; end
59
+ end
60
+ SimplyIndexedR = ::Rack::MockRequest.new(::Rack::Lint.new(SimplyIndexed.new))
61
+ SimplyIndexedUsedR = ::Rack::MockRequest.new(::Rack::Lint.new(SimplyIndexed.new(lambda{|env| [200,{},"#{3+nil}"]})))
62
+
63
+ # =============
64
+ # = Sessioned =
65
+ # =============
66
+
67
+ class Sessioned
68
+ include Minitrain
69
+ def set_val(val); @session[:val] = val; end
70
+ def get_val; @session[:val]; end
71
+ end
72
+ SessionedR = ::Rack::MockRequest.new(::Rack::Session::Cookie.new(::Rack::Lint.new(Sessioned.new)))
73
+
74
+ # =============
75
+ # = Helpers =
76
+ # =============
77
+
78
+ class UsingHelpers
79
+ include Minitrain
80
+ helpers { def hola(w); "Hola " + w; end }
81
+ def index; hola("mundo"); end
82
+ end
83
+ UsinghelpersR = ::Rack::MockRequest.new(::Rack::Lint.new(UsingHelpers.new))
84
+
85
+ # =========
86
+ # = Specs =
87
+ # =========
88
+
89
+ describe "Minitrain" do
90
+
91
+ it "Should dispatch on a method with no arguments" do
92
+ assert_equal "nothing",BasicR.get('/no_arg').body
93
+ end
94
+
95
+ it "Should dispatch on a method with arguments" do
96
+ assert_equal "a+b",BasicR.get('/with_args/a/b').body
97
+ end
98
+
99
+ it "Should dispatch on a method with splat argument" do
100
+ assert_equal "a+b+c+d",BasicR.get('/splat_arg/a/b/c/d').body
101
+ end
102
+
103
+ it "Should not dispatch if the method is private or does not exist" do
104
+ r = BasicR.get('/no_way')
105
+ assert_equal 404,r.status
106
+ assert_equal "NOT FOUND: /no_way",r.body
107
+ r = BasicR.get('/no')
108
+ assert_equal 404,r.status
109
+ assert_equal "NOT FOUND: /no",r.body
110
+ end
111
+
112
+ it "Should dispatch to appropriate underscored action when name contains '-' or '.'" do
113
+ assert_equal "<xml>test</xml>",BasicR.get('/best-restaurants.rss').body
114
+ end
115
+
116
+ it "Should only apply '-' and '.' substitution on action names" do
117
+ assert_equal 'best-restaurants.rss',IndexedR.get('/best-restaurants.rss').body
118
+ end
119
+
120
+ it "Should follow the rack stack if response is 404 and there are middlewares below" do
121
+ r = BasicLobsterR.get("/no_way")
122
+ assert_equal 200,r.status
123
+ end
124
+
125
+ it "Should provide filters" do
126
+ assert_equal "before+wrapped+after",FilterR.get('/wrapped').body
127
+ end
128
+
129
+ it "Should provide arguments in filter when page is not_found" do
130
+ assert_equal "a+b+c+dNOT FOUND: /a/b/c/d+after",FilterR.get('/a/b/c/d').body
131
+ end
132
+
133
+ it "Should send everything to :index if it exists and there is no matching method for first arg" do
134
+ assert_equal 'a+b+c+d',IndexedR.get('/exist/a/b/c/d').body
135
+ assert_equal 'a+b+c+d',IndexedR.get('/a/b/c/d').body
136
+ assert_equal '',IndexedR.get('/').body
137
+ end
138
+
139
+ it "Should send not_found if there is an argument error on handlers" do
140
+ assert_equal 200,SimplyIndexedR.get('/').status
141
+ assert_equal 404,SimplyIndexedR.get('/unknown').status
142
+ assert_equal 404,SimplyIndexedR.get('/will_fail/useless').status
143
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
144
+ end
145
+
146
+ it "Should handle errors without raising an exception unless in dev mode" do
147
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
148
+ ENV['RACK_ENV'] = 'development'
149
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
150
+ ENV['RACK_ENV'] = 'production'
151
+ @old_stdout = $stdout
152
+ $stdout = StringIO.new
153
+ res = SimplyIndexedR.get('/will_fail')
154
+ logged = $stdout.dup
155
+ $stdout = @old_stdout
156
+ assert_equal res.status,500
157
+ assert_match logged.string, /ArgumentError/
158
+ ENV['RACK_ENV'] = nil
159
+ end
160
+
161
+ it "Should not use the error handler if the error occurs further down the rack stack" do
162
+ ENV['RACK_ENV'] = 'production'
163
+ assert_raises(TypeError) { SimplyIndexedUsedR.get('/not_found') }
164
+ ENV['RACK_ENV'] = nil
165
+ end
166
+
167
+ it "Should set dispatch-specific variables correctly when defaulting to :index" do
168
+ assert_equal "action=index args=a,b,c,d a+b+c+d",IndexedR.get('/a/b/c/d?switch=true').body
169
+ end
170
+
171
+ it "Should have a shortcut for session hash" do
172
+ res = SessionedR.get('/set_val/ichigo')
173
+ res_2 = SessionedR.get('/get_val', 'HTTP_COOKIE'=>res["Set-Cookie"])
174
+ assert_equal 'ichigo',res_2.body
175
+ end
176
+
177
+ it "Should catch :response if needed" do
178
+ assert_equal BasicR.get('/test_throw').body,'Growl'
179
+ end
180
+
181
+ it "Should response with the helpers content" do
182
+ assert_equal UsinghelpersR.get('/').body,'Hola mundo'
183
+ end
184
+
185
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini-train
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Carlos Esquer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tilt
16
+ requirement: &71720560 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.2.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *71720560
25
+ description: This is a simplistic web framework
26
+ email: carlitos.esquer@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - README.md
32
+ - lib/minitrain.rb
33
+ - mini-train-0.1.0.gem
34
+ - mini-train.gemspec
35
+ - sample/app.rb
36
+ - sample/config.ru
37
+ - sample/views/index.erb
38
+ - sample/views/index.haml
39
+ - sample/views/index.slim
40
+ - test/minitrain-test.rb
41
+ homepage: http://rubymx.blogspot.com
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - ./lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.11
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: An attempt to create a web mini-framework forked from an rack app (rack-golem)
65
+ test_files: []