merb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +71 -14
- data/Rakefile +20 -31
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
- data/lib/merb.rb +3 -2
- data/lib/merb/caching.rb +5 -0
- data/lib/merb/caching/action_cache.rb +56 -0
- data/lib/merb/caching/fragment_cache.rb +38 -0
- data/lib/merb/caching/store/file_cache.rb +82 -0
- data/lib/merb/caching/store/memory_cache.rb +67 -0
- data/lib/merb/core_ext.rb +1 -1
- data/lib/merb/core_ext/merb_hash.rb +35 -0
- data/lib/merb/core_ext/merb_object.rb +88 -2
- data/lib/merb/merb_controller.rb +71 -69
- data/lib/merb/merb_dispatcher.rb +72 -0
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +19 -47
- data/lib/merb/merb_mailer.rb +1 -1
- data/lib/merb/merb_request.rb +11 -3
- data/lib/merb/merb_router.rb +113 -8
- data/lib/merb/merb_server.rb +71 -12
- data/lib/merb/merb_upload_handler.rb +8 -6
- data/lib/merb/merb_upload_progress.rb +1 -1
- data/lib/merb/merb_view_context.rb +13 -3
- data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
- data/lib/merb/mixins/controller_mixin.rb +149 -17
- data/lib/merb/mixins/form_control_mixin.rb +1 -0
- data/lib/merb/mixins/render_mixin.rb +148 -151
- data/lib/merb/mixins/responder_mixin.rb +133 -18
- data/lib/merb/mixins/view_context_mixin.rb +24 -0
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +6 -5
- data/lib/merb/template.rb +10 -0
- data/lib/merb/template/erubis.rb +52 -0
- data/lib/merb/template/haml.rb +77 -0
- data/lib/merb/template/markaby.rb +48 -0
- data/lib/merb/template/xml_builder.rb +34 -0
- metadata +19 -17
- data/lib/merb/mixins/merb_status_codes.rb +0 -59
data/README
CHANGED
@@ -6,8 +6,6 @@ Merb. Mongrel+Erb
|
|
6
6
|
Little bitty lightweight ruby app server. For when you really need performance
|
7
7
|
for simple dynamic pages.
|
8
8
|
|
9
|
-
**Sample APP included**
|
10
|
-
|
11
9
|
** Dependencies **
|
12
10
|
mongrel
|
13
11
|
erubis
|
@@ -36,36 +34,95 @@ controller class.
|
|
36
34
|
|
37
35
|
*RouteMatcher and route compiler*
|
38
36
|
Reads your route definition and compiles
|
39
|
-
a method on the fly that will match the request path against each route and do the
|
40
|
-
|
37
|
+
a method on the fly that will match the request path against each route and do the right thing.
|
38
|
+
|
39
|
+
*** NEW RESTFULL ROUTES ***
|
40
|
+
|
41
|
+
note the r.resource :posts macro. That makes it possible to use a restfull crud style controller for the posts resource
|
41
42
|
|
42
43
|
Merb::RouteMatcher.prepare do |r|
|
43
|
-
r.
|
44
|
+
r.resources :posts
|
44
45
|
r.add '/:controller/:action/:id'
|
45
|
-
r.add '/
|
46
|
+
r.add '/', :controller => 'files', :action => 'index'
|
46
47
|
end
|
47
48
|
|
48
49
|
Will be compiled and defined as a method with this lambda as the body:
|
49
50
|
|
50
51
|
lambda{|path|
|
51
52
|
case path
|
52
|
-
when Regexp.new('/
|
53
|
-
@sections[:
|
54
|
-
|
55
|
-
|
53
|
+
when Regexp.new('/posts/([^/,?]+)[;]edit')
|
54
|
+
@sections[:id] = $1
|
55
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"edit"}}.update(@sections)
|
56
|
+
when Regexp.new('/posts/new[;]([^/,?]+)')
|
57
|
+
@sections[:action] = $1
|
58
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"new", :get=>"new", :delete=>"new", :put=>"new"}}.update(@sections)
|
59
|
+
when Regexp.new('/posts/new')
|
60
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"new"}}.update(@sections)
|
61
|
+
when Regexp.new('/posts/([^/,?]+)\.([^/,?]+)')
|
62
|
+
@sections[:id] = $1
|
63
|
+
@sections[:format] = $2
|
64
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"show", :delete=>"destroy", :put=>"update"}}.update(@sections)
|
65
|
+
when Regexp.new('/posts\.([^/,?]+)')
|
66
|
+
@sections[:format] = $1
|
67
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"create", :get=>"index"}}.update(@sections)
|
68
|
+
when Regexp.new('/posts/([^/,?]+)')
|
69
|
+
@sections[:id] = $1
|
70
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"show", :delete=>"destroy", :put=>"update"}}.update(@sections)
|
71
|
+
when Regexp.new('/posts/?')
|
72
|
+
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"create", :get=>"index"}}.update(@sections)
|
56
73
|
when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
|
57
74
|
@sections[:controller] = $1
|
58
75
|
@sections[:action] = $2 || 'index'
|
59
|
-
@sections[:id] = $3
|
76
|
+
@sections[:id] = $3 if $3
|
60
77
|
return @sections
|
61
|
-
when Regexp.new('/
|
62
|
-
@sections
|
63
|
-
return {:controller=>"Test", :action=>"glob"}.merge(@sections)
|
78
|
+
when Regexp.new('/')
|
79
|
+
return {:controller=>"files", :action=>"index"}.update(@sections)
|
64
80
|
else
|
65
81
|
return {:controller=>'Noroutefound', :action=>'noroute'}
|
66
82
|
end
|
67
83
|
}
|
68
84
|
|
85
|
+
|
86
|
+
*Restful Controller*
|
87
|
+
|
88
|
+
restful controllers use a different dispatch system based on the request method verbs.
|
89
|
+
|
90
|
+
class Posts < Merb::Controller
|
91
|
+
# GET /posts
|
92
|
+
# GET /posts.xml
|
93
|
+
def index
|
94
|
+
end
|
95
|
+
|
96
|
+
# GET /posts/1
|
97
|
+
# GET /posts/1.xml
|
98
|
+
def show
|
99
|
+
end
|
100
|
+
|
101
|
+
# GET /posts/new
|
102
|
+
def new
|
103
|
+
end
|
104
|
+
|
105
|
+
# GET /posts/1;edit
|
106
|
+
def edit
|
107
|
+
end
|
108
|
+
|
109
|
+
# POST /posts
|
110
|
+
# POST /posts.xml
|
111
|
+
def create
|
112
|
+
end
|
113
|
+
|
114
|
+
# PUT /posts/1
|
115
|
+
# PUT /posts/1.xml
|
116
|
+
def update
|
117
|
+
end
|
118
|
+
|
119
|
+
# DELETE /posts/1
|
120
|
+
# DELETE /posts/1.xml
|
121
|
+
def destroy
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
69
126
|
*Simple Controllers*
|
70
127
|
classes with built in render method and template handling
|
71
128
|
with instance vars available in the views automatically. Merb also supports
|
data/Rakefile
CHANGED
@@ -4,24 +4,19 @@ require 'rake/gempackagetask'
|
|
4
4
|
require 'rake/rdoctask'
|
5
5
|
require 'rake/testtask'
|
6
6
|
require 'spec/rake/spectask'
|
7
|
-
require 'code_statistics'
|
8
7
|
require 'fileutils'
|
9
8
|
def __DIR__
|
10
9
|
File.dirname(__FILE__)
|
11
10
|
end
|
12
11
|
|
13
12
|
require __DIR__+'/tools/rakehelp'
|
13
|
+
require __DIR__+'/tools/annotation_extract'
|
14
14
|
include FileUtils
|
15
15
|
|
16
16
|
|
17
17
|
NAME = "merb"
|
18
|
-
VERS = "0.
|
18
|
+
VERS = "0.2.0"
|
19
19
|
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
20
|
-
RDOC_OPTS = ['--quiet', '--title', "Merb Documentation",
|
21
|
-
"--opname", "index.html",
|
22
|
-
"--line-numbers", 'TODO',
|
23
|
-
"--main", "README",
|
24
|
-
"--inline-source"]
|
25
20
|
|
26
21
|
setup_clean [ "pkg", "lib/*.bundle", "*.gem",
|
27
22
|
"doc", ".config", "examples/sample_app/dist/public/files/**/*", 'examples/sample_app/log/*']
|
@@ -34,22 +29,33 @@ task :merb => [:clean, :rdoc, :package]
|
|
34
29
|
task :doc => [:rdoc]
|
35
30
|
|
36
31
|
|
32
|
+
#Rake::RDocTask.new do |rdoc|
|
33
|
+
# rdoc.rdoc_dir = 'doc/rdoc'
|
34
|
+
# rdoc.options += RDOC_OPTS
|
35
|
+
# rdoc.main = "README"
|
36
|
+
# rdoc.title = "Merb Documentation"
|
37
|
+
# rdoc.rdoc_files.add ['README', 'LICENSE', 'TODO', 'lib/**/*.rb']
|
38
|
+
#end
|
37
39
|
Rake::RDocTask.new do |rdoc|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
files =['README', 'LICENSE', 'TODO', 'lib/**/*.rb']
|
41
|
+
rdoc.rdoc_files.add(files)
|
42
|
+
rdoc.main = "README" # page to start on
|
43
|
+
rdoc.title = "Merb Docs"
|
44
|
+
rdoc.template = __DIR__+"/tools/allison/allison.rb"
|
45
|
+
rdoc.rdoc_dir = 'doc' # rdoc output folder
|
46
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
43
47
|
end
|
44
48
|
|
49
|
+
|
50
|
+
|
45
51
|
spec = Gem::Specification.new do |s|
|
46
52
|
s.name = NAME
|
47
53
|
s.version = VERS
|
48
54
|
s.platform = Gem::Platform::RUBY
|
49
55
|
s.has_rdoc = true
|
50
56
|
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
51
|
-
s.rdoc_options += RDOC_OPTS +
|
52
|
-
|
57
|
+
#s.rdoc_options += RDOC_OPTS +
|
58
|
+
# ['--exclude', '^(app|uploads)']
|
53
59
|
s.summary = "Merb == Mongrel + Erb. Pocket rocket web framework."
|
54
60
|
s.description = s.summary
|
55
61
|
s.author = "Ezra Zygmuntowicz"
|
@@ -144,23 +150,6 @@ Spec::Rake::SpecTask.new('rcov') do |t|
|
|
144
150
|
t.rcov = true
|
145
151
|
end
|
146
152
|
|
147
|
-
##############################################################################
|
148
|
-
# Statistics
|
149
|
-
##############################################################################
|
150
|
-
|
151
|
-
STATS_DIRECTORIES = [
|
152
|
-
%w(Code lib/),
|
153
|
-
%w(Unit\ tests test/unit),
|
154
|
-
%w(Functional\ tests test/functional)
|
155
|
-
].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
|
156
|
-
|
157
|
-
desc "Report code statistics (KLOCs, etc) from the application"
|
158
|
-
task :stats do
|
159
|
-
#require 'extra/stats'
|
160
|
-
verbose = true
|
161
|
-
CodeStatistics.new(*STATS_DIRECTORIES).to_s
|
162
|
-
end
|
163
|
-
|
164
153
|
##############################################################################
|
165
154
|
# SVN
|
166
155
|
##############################################################################
|
data/examples/skeleton.tar
CHANGED
Binary file
|
@@ -5,8 +5,8 @@ class AddSessionsTable < ActiveRecord::Migration
|
|
5
5
|
t.column :session_id, :string, :limit => 32
|
6
6
|
t.column :created_at, :datetime
|
7
7
|
t.column :data, :text
|
8
|
-
add_index "sessions", ["session_id"], :name => "session_id_index"
|
9
8
|
end
|
9
|
+
add_index "sessions", ["session_id"], :name => "session_id_index"
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.down
|
data/lib/merb.rb
CHANGED
@@ -12,7 +12,7 @@ end
|
|
12
12
|
|
13
13
|
|
14
14
|
module Merb
|
15
|
-
VERSION='0.
|
15
|
+
VERSION='0.2.0' unless defined?VERSION
|
16
16
|
class Server
|
17
17
|
class << self
|
18
18
|
def config
|
@@ -47,6 +47,7 @@ MERB_FRAMEWORK_ROOT = __DIR__
|
|
47
47
|
|
48
48
|
MERB_ROOT = Merb::Server.merb_root || Dir.pwd
|
49
49
|
DIST_ROOT = Merb::Server.dist_root || Dir.pwd+'/dist'
|
50
|
+
MERB_ENV = Merb::Server.config[:environment]
|
50
51
|
|
51
52
|
logpath = $TESTING ? "/tmp/merb_test.log" : "#{MERB_ROOT}/log/merb.#{Merb::Server.port}.log"
|
52
53
|
MERB_LOGGER = Logger.new(logpath)
|
@@ -101,4 +102,4 @@ class Merb::Controller
|
|
101
102
|
include ::Merb::Authentication
|
102
103
|
puts "Basic Authentication mixed in"
|
103
104
|
end
|
104
|
-
end
|
105
|
+
end
|
data/lib/merb/caching.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Merb
|
2
|
+
module Caching
|
3
|
+
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
def self.included(base) # :nodoc:
|
7
|
+
base.class_eval {
|
8
|
+
def caching_enabled?
|
9
|
+
@_caching_enabled ||= ::Merb::Server.cache_templates
|
10
|
+
end
|
11
|
+
}
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Cache the given actions.
|
18
|
+
|
19
|
+
def cache_action(*actions)
|
20
|
+
return unless caching_enabled?
|
21
|
+
before :_get_action_fragment, :only => actions
|
22
|
+
after :_store_action_fragment, :only => actions
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def _get_action_fragment
|
30
|
+
fragment_name = "#{params[:action]}#{@request.query_string}"
|
31
|
+
if fragment = ::Merb::Caching::Fragment.get(fragment_name)
|
32
|
+
throw :halt, fragment
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _store_action_fragment
|
37
|
+
fragment_name = "#{params[:action]}#{@request.query_string}"
|
38
|
+
::Merb::Caching::Fragment.put(fragment_name, @body)
|
39
|
+
end
|
40
|
+
|
41
|
+
def caching_enabled?
|
42
|
+
@_caching_enabled ||= ::Merb::Server.cache_templates
|
43
|
+
end
|
44
|
+
|
45
|
+
def expire_action(*actions)
|
46
|
+
return unless caching_enabled?
|
47
|
+
for action in [actions].flatten
|
48
|
+
expire_fragment(action)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Merb
|
2
|
+
module Caching
|
3
|
+
module Fragment
|
4
|
+
class << self
|
5
|
+
def cache
|
6
|
+
@cache ||= determine_cache_store
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(name)
|
10
|
+
cache.get(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def put(name, content = nil)
|
14
|
+
cache.put(name, content)
|
15
|
+
content
|
16
|
+
end
|
17
|
+
|
18
|
+
def expire_fragment(name)
|
19
|
+
cache.delete(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def determine_cache_store
|
23
|
+
case ::Merb::Server.cache_store
|
24
|
+
when "file", :file
|
25
|
+
require 'merb/caching/store/file_cache'
|
26
|
+
::Merb::Caching::Store::FileCache.new
|
27
|
+
when "memory", :memory
|
28
|
+
require 'merb/caching/store/memory_cache'
|
29
|
+
::Merb::Caching::Store::MemoryCache.new
|
30
|
+
else
|
31
|
+
require 'merb/caching/store/memory_cache'
|
32
|
+
::Merb::Caching::Store::MemoryCache.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module Merb
|
6
|
+
module Caching
|
7
|
+
module Store
|
8
|
+
|
9
|
+
class FileCache
|
10
|
+
|
11
|
+
def initialize(name = "cache", keepalive = nil)
|
12
|
+
@path = File.join(MERB_ROOT, name)
|
13
|
+
@keepalive = keepalive
|
14
|
+
|
15
|
+
FileUtils.mkdir_p(@path, :mode => 0700)
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(k,v)
|
19
|
+
fn = File.join(@path, escape_filename(k.to_s) )
|
20
|
+
encode_file(fn, v)
|
21
|
+
end
|
22
|
+
alias :put :[]=
|
23
|
+
alias :write :[]=
|
24
|
+
|
25
|
+
def [](k)
|
26
|
+
fn = File.join(@path, escape_filename(k.to_s) )
|
27
|
+
return nil unless File.exists?(fn)
|
28
|
+
decode_file(fn)
|
29
|
+
end
|
30
|
+
alias :get :[]
|
31
|
+
alias_method :read, :[]
|
32
|
+
|
33
|
+
def delete(k)
|
34
|
+
f = File.join(@path, escape_filename(k.to_s))
|
35
|
+
File.delete(f) if File.exists?(f)
|
36
|
+
end
|
37
|
+
|
38
|
+
def gc!
|
39
|
+
return unless @keepalive
|
40
|
+
|
41
|
+
now = Time.now
|
42
|
+
all.each do |fn|
|
43
|
+
expire_time = File.stat(fn).atime + @keepalive
|
44
|
+
File.delete(fn) if now > expire_time
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def decode_file(fn)
|
51
|
+
val = nil
|
52
|
+
File.open(fn,"rb") do |f|
|
53
|
+
f.flock(File::LOCK_EX)
|
54
|
+
val = Marshal.load( f.read )
|
55
|
+
f.flock(File::LOCK_UN)
|
56
|
+
end
|
57
|
+
return val
|
58
|
+
end
|
59
|
+
|
60
|
+
def encode_file(fn, value)
|
61
|
+
File.open(fn, "wb") do |f|
|
62
|
+
f.flock(File::LOCK_EX)
|
63
|
+
f.chmod(0600)
|
64
|
+
f.write(Marshal.dump(value))
|
65
|
+
f.flock(File::LOCK_UN)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def all
|
70
|
+
Dir.glob( File.join(@path, '*' ) )
|
71
|
+
end
|
72
|
+
# need this for fat filesystems
|
73
|
+
def escape_filename(fn)
|
74
|
+
URI.escape(fn, /["\/:;|=,\[\]]/)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Merb
|
2
|
+
module Caching
|
3
|
+
module Store
|
4
|
+
|
5
|
+
class MemoryCache
|
6
|
+
|
7
|
+
def initialize(opts={})
|
8
|
+
@opts = opts
|
9
|
+
@cache = Hash.new
|
10
|
+
@timestamps = Hash.new
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@cache_ttl = @opts.fetch(:session_ttl, 30*60) # default 30 minutes
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@mutex.synchronize {
|
17
|
+
@timestamps[key] = Time.now
|
18
|
+
@cache[key]
|
19
|
+
}
|
20
|
+
end
|
21
|
+
alias_method :get, :[]
|
22
|
+
alias_method :read, :[]
|
23
|
+
|
24
|
+
def []=(key, val)
|
25
|
+
@mutex.synchronize {
|
26
|
+
@timestamps[key] = Time.now
|
27
|
+
@cache[key] = val
|
28
|
+
}
|
29
|
+
end
|
30
|
+
alias_method :put, :[]=
|
31
|
+
alias_method :write, :[]=
|
32
|
+
|
33
|
+
def delete(key)
|
34
|
+
@mutex.synchronize {
|
35
|
+
@cache.delete(key)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
alias_method :remove, :delete
|
39
|
+
|
40
|
+
def delete_if(&block)
|
41
|
+
@hash.delete_if(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def reap_old_caches
|
45
|
+
@timestamps.each do |key,stamp|
|
46
|
+
if stamp + @cache_ttl < Time.now
|
47
|
+
delete(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
GC.start
|
51
|
+
end
|
52
|
+
|
53
|
+
def cache
|
54
|
+
@cache
|
55
|
+
end
|
56
|
+
|
57
|
+
def keys
|
58
|
+
@cache.keys
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|