merb 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|