merb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README +71 -14
  2. data/Rakefile +20 -31
  3. data/examples/skeleton.tar +0 -0
  4. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
  5. data/lib/merb.rb +3 -2
  6. data/lib/merb/caching.rb +5 -0
  7. data/lib/merb/caching/action_cache.rb +56 -0
  8. data/lib/merb/caching/fragment_cache.rb +38 -0
  9. data/lib/merb/caching/store/file_cache.rb +82 -0
  10. data/lib/merb/caching/store/memory_cache.rb +67 -0
  11. data/lib/merb/core_ext.rb +1 -1
  12. data/lib/merb/core_ext/merb_hash.rb +35 -0
  13. data/lib/merb/core_ext/merb_object.rb +88 -2
  14. data/lib/merb/merb_controller.rb +71 -69
  15. data/lib/merb/merb_dispatcher.rb +72 -0
  16. data/lib/merb/merb_exceptions.rb +6 -1
  17. data/lib/merb/merb_handler.rb +19 -47
  18. data/lib/merb/merb_mailer.rb +1 -1
  19. data/lib/merb/merb_request.rb +11 -3
  20. data/lib/merb/merb_router.rb +113 -8
  21. data/lib/merb/merb_server.rb +71 -12
  22. data/lib/merb/merb_upload_handler.rb +8 -6
  23. data/lib/merb/merb_upload_progress.rb +1 -1
  24. data/lib/merb/merb_view_context.rb +13 -3
  25. data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
  26. data/lib/merb/mixins/controller_mixin.rb +149 -17
  27. data/lib/merb/mixins/form_control_mixin.rb +1 -0
  28. data/lib/merb/mixins/render_mixin.rb +148 -151
  29. data/lib/merb/mixins/responder_mixin.rb +133 -18
  30. data/lib/merb/mixins/view_context_mixin.rb +24 -0
  31. data/lib/merb/session/merb_ar_session.rb +4 -4
  32. data/lib/merb/session/merb_memory_session.rb +6 -5
  33. data/lib/merb/template.rb +10 -0
  34. data/lib/merb/template/erubis.rb +52 -0
  35. data/lib/merb/template/haml.rb +77 -0
  36. data/lib/merb/template/markaby.rb +48 -0
  37. data/lib/merb/template/xml_builder.rb +34 -0
  38. metadata +19 -17
  39. 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
- right thing. So the following routes:
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.add '/foo/:bar/baz/:id', :controller => 'Test', :action => 'foo'
44
+ r.resources :posts
44
45
  r.add '/:controller/:action/:id'
45
- r.add '/bar/:*rest', :controller => 'Test', :action => 'glob'
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('/foo/([^/;.,?]+)/baz/([^/;.,?]+)')
53
- @sections[:bar] = $1
54
- @sections[:id] = $2
55
- return {:controller=>"Test", :action=>"foo"}.merge(@sections)
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 || nil
76
+ @sections[:id] = $3 if $3
60
77
  return @sections
61
- when Regexp.new('/bar/([^;.,?]+)')
62
- @sections[:rest] = $1
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.1.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
- rdoc.rdoc_dir = 'doc/rdoc'
39
- rdoc.options += RDOC_OPTS
40
- rdoc.main = "README"
41
- rdoc.title = "Merb Documentation"
42
- rdoc.rdoc_files.add ['README', 'LICENSE', 'TODO', 'lib/**/*.rb']
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
- ['--exclude', '^(app|uploads)']
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
  ##############################################################################
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
@@ -12,7 +12,7 @@ end
12
12
 
13
13
 
14
14
  module Merb
15
- VERSION='0.1.0' unless defined?VERSION
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
@@ -0,0 +1,5 @@
1
+ corelib = __DIR__+'/merb/caching'
2
+
3
+ %w[ action_cache
4
+ fragment_cache
5
+ ].each {|fn| require File.join(corelib, fn)}
@@ -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