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.
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