merb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,10 +1,9 @@
1
- <pre>
1
+
2
2
  Copyright (c) 2006 Ezra Zygmuntowicz
3
3
 
4
- Merb. Mongrel+Erb
4
+ Merb.
5
5
 
6
- Little bitty lightweight ruby app server. For when you really need performance
7
- for simple dynamic pages.
6
+ Lightweight MVC Ruby app server. For high performance dynamic pages.
8
7
 
9
8
  ** Dependencies **
10
9
  mongrel
@@ -33,6 +32,7 @@ params into params and the cookies into cookies when it instantiates your
33
32
  controller class.
34
33
 
35
34
  *RouteMatcher and route compiler*
35
+
36
36
  Reads your route definition and compiles
37
37
  a method on the fly that will match the request path against each route and do the right thing.
38
38
 
@@ -42,93 +42,52 @@ note the r.resource :posts macro. That makes it possible to use a restfull crud
42
42
 
43
43
  Merb::RouteMatcher.prepare do |r|
44
44
  r.resources :posts
45
- r.add '/:controller/:action/:id'
45
+ r.default_routes
46
46
  r.add '/', :controller => 'files', :action => 'index'
47
47
  end
48
48
 
49
- Will be compiled and defined as a method with this lambda as the body:
50
-
51
- lambda{|path|
52
- case path
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)
73
- when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
74
- @sections[:controller] = $1
75
- @sections[:action] = $2 || 'index'
76
- @sections[:id] = $3 if $3
77
- return @sections
78
- when Regexp.new('/')
79
- return {:controller=>"files", :action=>"index"}.update(@sections)
80
- else
81
- return {:controller=>'Noroutefound', :action=>'noroute'}
82
- end
83
- }
49
+ The r.default_routes routes adds the standard routes:
84
50
 
51
+ /controller/action/id.xml
52
+ /controller/action/id
53
+ /controller/action.xml
54
+ /controller/action
55
+ /controller.xml # index action
56
+ /controller # index action
85
57
 
86
- *Restful Controller*
87
58
 
88
- restful controllers use a different dispatch system based on the request method verbs.
89
59
 
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
-
126
- *Simple Controllers*
127
- classes with built in render method and template handling
60
+ *Controllers*
61
+ Classes with built in render method and template handling
128
62
  with instance vars available in the views automatically. Merb also supports
129
63
  layouts. It will look for a layout named after your controller class first and
130
64
  then fall back to application.herb if no layout exists named after your controller.
131
- You can use render_no_layout or do render :layout => :none
65
+ You can use render :layout => :none.
66
+
67
+ Merb does not automatically render for you in your controller actions, you have
68
+ to call render yourself. I consider this a big advantage over the way rails does
69
+ it for a few reasons. The main reason is that in rails you can only render once
70
+ per action, so it knows if you haven’t rendered it shoudl auto render. Merb on
71
+ the other hand, returns to the browser whatever the return value of your
72
+ controller’s action method is. This opens up more possibilities imho because
73
+ now you can return any string from your action and that will be sent down
74
+ the pipe. So Merb’s render method just returns a string and needs to be the
75
+ last thing you call in your action. You can render multiple times and capture
76
+ the results into @ivars and then render a master template with many embeded
77
+ templates. Also if you return a handle on a File or IO object from your action
78
+ then merb will hand that over to mongrel to be streamed out to the client. And
79
+ if you return a Proc object from your action, it will be called and the
80
+ return value sent to the client.
81
+
82
+ That last point has some cool connotations if you think about it. Merb does
83
+ have a mutex lock around the call to your controller’s action anywhere that
84
+ you can call AR objects. Merb’s lock is way smaller then rails giant lock
85
+ though and allows for many more concurrent requests to be handled by one
86
+ process. By returning a Proc object from your action, you allow merb to
87
+ release the lock and the proc is called in multi threaded way. This allows
88
+ for all kinds of cool streaming and ‘futures’ where you return the proc and
89
+ release the mutex. It’s basically like handing over the proc to mongrel and
90
+ mongrel handles calling it in a thread safe manner.
132
91
 
133
92
 
134
93
  class Test < Merb::Controller
@@ -141,6 +100,7 @@ end
141
100
 
142
101
  You can also render partials like so:
143
102
  <%= partial(:comments) %>
103
+
144
104
  This assumes a _comments.rhtml file in the same view dir as the current
145
105
  controller/view
146
106
 
@@ -179,14 +139,56 @@ $('comments').update('<%=js partial(:posts) %>');
179
139
  </ul>
180
140
 
181
141
 
142
+ *Restful Controllers*
143
+
144
+ restful controllers use a different dispatch system based on the request method verbs. Merb
145
+ supports multi return values based on the accept header via respond_to
146
+
147
+ class Posts < Merb::Controller
148
+ # GET /posts
149
+ # GET /posts.xml
150
+ def index
151
+ @posts = Post.find :all
152
+ respond_to {|format|
153
+ format.html { render }
154
+ format.js { render :js => 'index' }
155
+ format.xml { render :xml => @posts.to_xml }
156
+ }
157
+ end
158
+
159
+ # GET /posts/1
160
+ # GET /posts/1.xml
161
+ def show
162
+ end
163
+
164
+ # GET /posts/new
165
+ def new
166
+ end
167
+
168
+ # GET /posts/1;edit
169
+ def edit
170
+ end
171
+
172
+ # POST /posts
173
+ # POST /posts.xml
174
+ def create
175
+ end
176
+
177
+ # PUT /posts/1
178
+ # PUT /posts/1.xml
179
+ def update
180
+ end
181
+
182
+ # DELETE /posts/1
183
+ # DELETE /posts/1.xml
184
+ def destroy
185
+ end
186
+ end
187
+
188
+
182
189
  *Controllers have powerful before and after filters*
183
190
 
184
- Use the before method in your controllers. before accepts either a symbol
185
- or a Proc/lambda object. If you give it a symbol it will call a method with
186
- the same name as the symbol. If you give it a proc that takes one argument
187
- it will call the proc with the current controller as that argument. You can
188
- use :only and :exclude as options to your filters to exclude or include actionsfrom certain filters. :only and :exclude take :symbols or [:sym, :sam]
189
- array of symbols.
191
+ Use the before method in your controllers. before accepts either a symbol, string or a Proc/lambda object. If you give it a symbol it will call a method with the same name as the symbol. If you give it a proc that takes one argument it will call the proc with the current controller as that argument. You can use :only and :exclude as options to your filters to exclude or include actionsfrom certain filters. :only and :exclude take :symbols or [:sym, :sam] array of symbols.
190
192
 
191
193
  class Foo < Merb::Controller
192
194
 
@@ -224,21 +226,23 @@ throw :halt, Proc.new{ |c| c.redirect "/foo" }
224
226
 
225
227
  # halts the chain and returns whatever is in the string
226
228
 
227
- throw :halt, "<h1>You don't have permissions IDIOT!</h1>"
229
+ throw :halt, "<h1>You don't have permissions dude!</h1>"
228
230
 
229
231
  or even render templates:
230
232
 
231
233
  throw :halt, render 'foo'
232
234
  throw :halt, partial 'foo'
233
235
 
234
- After filters only accept a Proc and call that proc with the controller:
236
+ After filters accept a symbol, string or Proc and call that proc with the controller:
235
237
 
236
238
  after Proc.new {|c| Tidy.new(c.body) }, :only => :index
237
239
 
238
- Sessions are available when you start merb with the sql_session set to true or the memory_session set to true. See generated app for migration too add session table.
239
-
240
- Helpers: dist/app/helpers/global_helper.rb will be available to all of your views. Helpers named afdter your controller plus _helper.rb will be included in the views for that controller only.
240
+ Sessions are available when you start merb with the sql_session set to true or the
241
+ memory_session set to true. See generated app for migration too add session table.
241
242
 
243
+ Helpers: dist/app/helpers/global_helper.rb will be available to all of your views.
244
+ Helpers named after your controller plus _helper.rb will be included in the views
245
+ for that controller only.
242
246
 
243
247
  *The merb server*
244
248
  right now you add your routes in
@@ -264,7 +268,7 @@ $merb -i
264
268
  *File uploads*
265
269
  This is one of the things that Merb was written for. Rails doesn't allow
266
270
  multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. Merb allows multiple file uploads at once.
267
- progress bar. When a file is uploaded with Merb, it gets put in a Tempfile. So
271
+ When a file is uploaded with Merb, it gets put in a Tempfile. So
268
272
  you just want to copy it to the right place on the filesystem.
269
273
 
270
274
  def upload
@@ -275,6 +279,13 @@ def upload
275
279
  render
276
280
  end
277
281
 
282
+ A file upload will have a hash of params like this:
283
+ {
284
+ :filename => File.basename(filename),
285
+ :content_type => content_type,
286
+ :tempfile => <Tempfile>,
287
+ :size => File.size(body)
288
+ }
278
289
 
279
290
  *Merb app layout*
280
291
 
@@ -303,5 +314,4 @@ merb_app:
303
314
  lib
304
315
  public
305
316
  plugins
306
- schema
307
- </pre>
317
+ schema
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ include FileUtils
15
15
 
16
16
 
17
17
  NAME = "merb"
18
- VERS = "0.2.0"
18
+ VERS = "0.3.0"
19
19
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
20
20
 
21
21
  setup_clean [ "pkg", "lib/*.bundle", "*.gem",
@@ -91,8 +91,8 @@ end
91
91
  desc "rdoc to rubyforge"
92
92
  task :doc_rforge do
93
93
  sh %{rake doc}
94
- sh %{sudo chmod -R 755 doc/rdoc}
95
- sh %{scp -r -p doc/rdoc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
94
+ sh %{sudo chmod -R 755 doc}
95
+ sh %{scp -r -p doc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
96
96
  end
97
97
 
98
98
 
@@ -132,8 +132,7 @@ end
132
132
 
133
133
  desc 'Run all tests, specs and finish with rcov'
134
134
  task :aok do
135
- sh %{rake rcov}
136
- sh %{rake spec}
135
+ sh %{rake specs}
137
136
  end
138
137
 
139
138
  desc "Run all specs"
Binary file
@@ -11,9 +11,12 @@
11
11
 
12
12
  puts "Compiling routes.."
13
13
  Merb::RouteMatcher.prepare do |r|
14
+ # restfull routes
15
+ # r.resources :posts
16
+
14
17
  # default route, usually you don't want to change this
15
- r.add '/:controller/:action/:id'
18
+ r.default_routes
16
19
 
17
20
  # change this for your home page to be avaiable at /
18
- r.add '/', :controller => 'default', :action =>'index'
21
+ #r.add '/', :controller => 'whatever', :action =>'index'
19
22
  end
@@ -1,7 +1,6 @@
1
1
  class AddSessionsTable < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :sessions, :force => true do |t|
4
- t.column :id, :integer, :null => false
5
4
  t.column :session_id, :string, :limit => 32
6
5
  t.column :created_at, :datetime
7
6
  t.column :data, :text
@@ -12,7 +12,7 @@ end
12
12
 
13
13
 
14
14
  module Merb
15
- VERSION='0.2.0' unless defined?VERSION
15
+ VERSION='0.3.0' unless defined?VERSION
16
16
  class Server
17
17
  class << self
18
18
  def config
@@ -69,10 +69,17 @@ MERB_LOGGER.level = case (Merb::Server.log_level.downcase rescue '')
69
69
  Logger::INFO
70
70
  end
71
71
 
72
-
72
+ module Mongrel::Const
73
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
74
+ QUERY_STRING = 'QUERY_STRING'.freeze
75
+ CONTENT_TYPE_TEXT_HTML_HASH = {'Content-Type' =>'text/html'}
76
+ APPLICATION_JSON = 'application/json'.freeze
77
+ TEXT_JSON = 'text/x-json'.freeze
78
+ UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
79
+ end
73
80
 
74
81
  lib = File.join(__DIR__, 'merb')
75
- Dir.entries(lib).sort.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
82
+ Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
76
83
 
77
84
  require File.join(__DIR__, 'merb/vendor/paginator/paginator')
78
85
 
@@ -1,6 +1,7 @@
1
1
  corelib = __DIR__+'/merb/core_ext'
2
2
 
3
- %w[ merb_class
3
+ %w[ merb_inflector
4
+ merb_class
4
5
  merb_kernel
5
6
  merb_object
6
7
  merb_enumerable
@@ -9,4 +10,5 @@ corelib = __DIR__+'/merb/core_ext'
9
10
  merb_hash
10
11
  merb_numeric
11
12
  merb_symbol
13
+
12
14
  ].each {|fn| require File.join(corelib, fn)}
@@ -1,106 +1,140 @@
1
- class Class # :nodoc:
2
-
3
- def cattr_reader(*syms)
4
- syms.flatten.each do |sym|
5
- class_eval(<<-EOS, __FILE__, __LINE__)
6
- unless defined? @@#{sym}
7
- @@#{sym} = nil
8
- end
1
+ # Retain for backward compatibility. Methods are now included in Class.
2
+ module ClassInheritableAttributes # :nodoc:
3
+ end
9
4
 
5
+ # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
6
+ # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
7
+ # to, for example, an array without those additions being shared with either their parent, siblings, or
8
+ # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
9
+ class Class # :nodoc:
10
+ def class_inheritable_reader(*syms)
11
+ syms.each do |sym|
12
+ next if sym.is_a?(Hash)
13
+ class_eval <<-EOS
10
14
  def self.#{sym}
11
- @@#{sym}
15
+ read_inheritable_attribute(:#{sym})
12
16
  end
13
17
 
14
18
  def #{sym}
15
- @@#{sym}
19
+ self.class.#{sym}
16
20
  end
17
21
  EOS
18
22
  end
19
23
  end
20
24
 
21
- def cattr_writer(*syms)
22
- syms.flatten.each do |sym|
23
- class_eval(<<-EOS, __FILE__, __LINE__)
24
- unless defined? @@#{sym}
25
- @@#{sym} = nil
26
- end
27
-
25
+ def class_inheritable_writer(*syms)
26
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
27
+ syms.each do |sym|
28
+ class_eval <<-EOS
28
29
  def self.#{sym}=(obj)
29
- @@#{sym} = obj
30
+ write_inheritable_attribute(:#{sym}, obj)
30
31
  end
31
32
 
33
+ #{"
32
34
  def #{sym}=(obj)
33
- @@#{sym} = obj
35
+ self.class.#{sym} = obj
34
36
  end
37
+ " unless options[:instance_writer] == false }
35
38
  EOS
36
39
  end
37
40
  end
38
41
 
39
- def cattr_accessor(*syms)
40
- cattr_reader(*syms)
41
- cattr_writer(*syms)
42
- end
43
-
44
- def shared_reader(*syms)
42
+ def class_inheritable_array_writer(*syms)
43
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
45
44
  syms.each do |sym|
46
45
  class_eval <<-EOS
47
- def self.#{sym}
48
- read_shared_attribute(:#{sym})
46
+ def self.#{sym}=(obj)
47
+ write_inheritable_array(:#{sym}, obj)
49
48
  end
50
49
 
51
- def #{sym}
52
- self.class.#{sym}
50
+ #{"
51
+ def #{sym}=(obj)
52
+ self.class.#{sym} = obj
53
53
  end
54
+ " unless options[:instance_writer] == false }
54
55
  EOS
55
56
  end
56
57
  end
57
58
 
58
- def shared_writer(*syms)
59
+ def class_inheritable_hash_writer(*syms)
60
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
59
61
  syms.each do |sym|
60
62
  class_eval <<-EOS
61
63
  def self.#{sym}=(obj)
62
- write_shared_attribute(:#{sym}, obj)
64
+ write_inheritable_hash(:#{sym}, obj)
63
65
  end
64
66
 
67
+ #{"
65
68
  def #{sym}=(obj)
66
69
  self.class.#{sym} = obj
67
70
  end
71
+ " unless options[:instance_writer] == false }
68
72
  EOS
69
73
  end
70
74
  end
71
75
 
72
- def shared_accessor(*syms)
73
- shared_reader(*syms)
74
- shared_writer(*syms)
76
+ def class_inheritable_accessor(*syms)
77
+ class_inheritable_reader(*syms)
78
+ class_inheritable_writer(*syms)
79
+ end
80
+
81
+ def class_inheritable_array(*syms)
82
+ class_inheritable_reader(*syms)
83
+ class_inheritable_array_writer(*syms)
84
+ end
85
+
86
+ def class_inheritable_hash(*syms)
87
+ class_inheritable_reader(*syms)
88
+ class_inheritable_hash_writer(*syms)
75
89
  end
76
90
 
77
- def shared_attributes
78
- @shared_attributes ||= {}
91
+ def inheritable_attributes
92
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
79
93
  end
80
94
 
81
- def write_shared_attribute(key, value)
82
- shared_attributes[key] = value
95
+ def write_inheritable_attribute(key, value)
96
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
97
+ @inheritable_attributes = {}
98
+ end
99
+ inheritable_attributes[key] = value
100
+ end
101
+
102
+ def write_inheritable_array(key, elements)
103
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
104
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
83
105
  end
84
106
 
85
- def read_shared_attribute(key)
86
- shared_attributes[key]
107
+ def write_inheritable_hash(key, hash)
108
+ write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
109
+ write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
110
+ end
111
+
112
+ def read_inheritable_attribute(key)
113
+ inheritable_attributes[key]
87
114
  end
88
115
 
89
- def reset_shared_attributes
90
- shared_attributes.clear
116
+ def reset_inheritable_attributes
117
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
91
118
  end
92
119
 
93
- private
94
- def inherited_with_shared_attributes(child)
95
- inherited_without_shared_attributes(child) if respond_to?(:inherited_without_shared_attributes)
120
+ private
121
+ # Prevent this constant from being created multiple times
122
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
123
+
124
+ def inherited_with_inheritable_attributes(child)
125
+ inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
96
126
 
97
- new_shared_attributes = shared_attributes.inject({}) do |memo, (key, value)|
98
- memo.update(key => (value.dup rescue value))
127
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
128
+ new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
129
+ else
130
+ new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
131
+ memo.update(key => (value.dup rescue value))
132
+ end
99
133
  end
100
134
 
101
- child.instance_variable_set('@shared_attributes', new_shared_attributes)
135
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
102
136
  end
103
137
 
104
- alias inherited_without_shared_attributes inherited
105
- alias inherited inherited_with_shared_attributes
106
- end
138
+ alias inherited_without_inheritable_attributes inherited
139
+ alias inherited inherited_with_inheritable_attributes
140
+ end