merb 0.3.4 → 0.3.7

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 (62) hide show
  1. data/README +206 -197
  2. data/Rakefile +12 -21
  3. data/bin/merb +1 -1
  4. data/examples/skeleton/Rakefile +6 -20
  5. data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
  6. data/examples/skeleton/dist/conf/database.yml +23 -0
  7. data/examples/skeleton/dist/conf/environments/development.rb +1 -0
  8. data/examples/skeleton/dist/conf/environments/production.rb +1 -0
  9. data/examples/skeleton/dist/conf/environments/test.rb +1 -0
  10. data/examples/skeleton/dist/conf/merb.yml +32 -28
  11. data/examples/skeleton/dist/conf/merb_init.rb +16 -13
  12. data/examples/skeleton/dist/conf/router.rb +9 -9
  13. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
  14. data/lib/merb.rb +23 -18
  15. data/lib/merb/caching/fragment_cache.rb +3 -7
  16. data/lib/merb/caching/store/memcache.rb +20 -0
  17. data/lib/merb/core_ext/merb_array.rb +0 -0
  18. data/lib/merb/core_ext/merb_class.rb +44 -4
  19. data/lib/merb/core_ext/merb_enumerable.rb +43 -1
  20. data/lib/merb/core_ext/merb_hash.rb +200 -122
  21. data/lib/merb/core_ext/merb_kernel.rb +2 -0
  22. data/lib/merb/core_ext/merb_module.rb +41 -0
  23. data/lib/merb/core_ext/merb_numeric.rb +57 -5
  24. data/lib/merb/core_ext/merb_object.rb +172 -6
  25. data/lib/merb/generators/merb_app/merb_app.rb +15 -9
  26. data/lib/merb/merb_abstract_controller.rb +193 -0
  27. data/lib/merb/merb_constants.rb +26 -1
  28. data/lib/merb/merb_controller.rb +143 -234
  29. data/lib/merb/merb_dispatcher.rb +28 -20
  30. data/lib/merb/merb_drb_server.rb +2 -3
  31. data/lib/merb/merb_exceptions.rb +194 -49
  32. data/lib/merb/merb_handler.rb +34 -26
  33. data/lib/merb/merb_mail_controller.rb +200 -0
  34. data/lib/merb/merb_mailer.rb +33 -13
  35. data/lib/merb/merb_part_controller.rb +42 -0
  36. data/lib/merb/merb_plugins.rb +293 -0
  37. data/lib/merb/merb_request.rb +6 -4
  38. data/lib/merb/merb_router.rb +99 -65
  39. data/lib/merb/merb_server.rb +65 -21
  40. data/lib/merb/merb_upload_handler.rb +2 -1
  41. data/lib/merb/merb_view_context.rb +36 -15
  42. data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
  43. data/lib/merb/mixins/controller_mixin.rb +67 -28
  44. data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
  45. data/lib/merb/mixins/form_control_mixin.rb +280 -42
  46. data/lib/merb/mixins/render_mixin.rb +127 -45
  47. data/lib/merb/mixins/responder_mixin.rb +5 -7
  48. data/lib/merb/mixins/view_context_mixin.rb +260 -94
  49. data/lib/merb/session.rb +23 -0
  50. data/lib/merb/session/merb_ar_session.rb +28 -16
  51. data/lib/merb/session/merb_mem_cache_session.rb +108 -0
  52. data/lib/merb/session/merb_memory_session.rb +65 -20
  53. data/lib/merb/template/erubis.rb +22 -13
  54. data/lib/merb/template/haml.rb +5 -16
  55. data/lib/merb/template/markaby.rb +5 -3
  56. data/lib/merb/template/xml_builder.rb +17 -5
  57. data/lib/merb/test/merb_fake_request.rb +63 -0
  58. data/lib/merb/test/merb_multipart.rb +58 -0
  59. data/lib/tasks/db.rake +2 -0
  60. data/lib/tasks/merb.rake +20 -8
  61. metadata +24 -25
  62. data/examples/skeleton.tar +0 -0
data/Rakefile CHANGED
@@ -15,11 +15,11 @@ include FileUtils
15
15
 
16
16
 
17
17
  NAME = "merb"
18
- VERS = "0.3.4"
18
+ VERS = "0.3.7"
19
19
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
20
20
 
21
21
  setup_clean [ "pkg", "lib/*.bundle", "*.gem",
22
- "doc", ".config", "examples/sample_app/dist/public/files/**/*", 'examples/sample_app/log/*']
22
+ "doc", ".config", "examples/sample_app/dist/public/files/**/*", 'examples/sample_app/log/*', 'coverage']
23
23
 
24
24
 
25
25
  desc "Packages up Merb."
@@ -60,8 +60,6 @@ spec = Gem::Specification.new do |s|
60
60
  s.add_dependency('erubis')
61
61
  s.add_dependency('json')
62
62
  s.add_dependency('mime-types')
63
- s.add_dependency('xml-simple')
64
- s.add_dependency('archive-tar-minitar')
65
63
  s.required_ruby_version = '>= 1.8.4'
66
64
 
67
65
  s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{bin,test,lib,examples}/**/*")
@@ -91,20 +89,6 @@ task :doc_rforge do
91
89
  sh %{scp -r -p doc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
92
90
  end
93
91
 
94
-
95
- desc "Pack skeleton app as a tar.gz file"
96
- task :skeleton do
97
- require 'archive/tar/minitar'
98
- include Archive::Tar
99
- Find.find(File.join(__DIR__,'examples/skeleton')) do |f|
100
- FileUtils.rm_rf(f) if /\.svn$/ =~ f
101
- end
102
- File.open(File.join(__DIR__,'examples/skeleton.tar'), 'wb') do |tar|
103
- Dir.chdir 'examples/skeleton'
104
- Minitar.pack('.', tar, true)
105
- end
106
- end
107
-
108
92
  desc 'Run unit tests'
109
93
  Rake::TestTask.new('test_unit') do |t|
110
94
  t.libs << 'test'
@@ -135,13 +119,20 @@ desc "Run all specs"
135
119
  Spec::Rake::SpecTask.new('specs') do |t|
136
120
  t.spec_opts = ["--format", "specdoc"]
137
121
  t.libs = ['lib', 'server/lib' ]
138
- t.spec_files = FileList['specs/**/*_spec.rb']
122
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
123
+ end
124
+
125
+ desc "Run all specs output html"
126
+ Spec::Rake::SpecTask.new('specs_html') do |t|
127
+ t.spec_opts = ["--format", "html"]
128
+ t.libs = ['lib', 'server/lib' ]
129
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
139
130
  end
140
131
 
141
132
  desc "RCov"
142
133
  Spec::Rake::SpecTask.new('rcov') do |t|
143
134
  t.spec_opts = ["--format", "specdoc"]
144
- t.spec_files = FileList['specs/**/*_spec.rb']
135
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
145
136
  t.libs = ['lib', 'server/lib' ]
146
137
  t.rcov = true
147
138
  end
@@ -166,4 +157,4 @@ end
166
157
  desc "Add new files to subversion"
167
158
  task :svn_add do
168
159
  system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
169
- end
160
+ end
data/bin/merb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
4
  require 'merb/merb_server'
@@ -1,10 +1,13 @@
1
1
  require 'rake'
2
2
  require 'rake/rdoctask'
3
3
  require 'rake/testtask'
4
- require 'code_statistics'
5
4
  require 'fileutils'
6
5
  require 'rubygems'
7
- require 'merb'
6
+ if File.directory?( File.join(File.dirname(__FILE__), "dist/framework"))
7
+ require File.join(File.dirname(__FILE__), "dist/framework/merb")
8
+ else
9
+ require 'merb'
10
+ end
8
11
  require MERB_FRAMEWORK_ROOT+'/merb_tasks'
9
12
  MERB_ROOT = File.dirname(__FILE__)
10
13
  include FileUtils
@@ -55,23 +58,6 @@ task :aok do
55
58
  sh %{rake spec}
56
59
  end
57
60
 
58
- ##############################################################################
59
- # Statistics
60
- ##############################################################################
61
-
62
- STATS_DIRECTORIES = [
63
- %w(Code lib/),
64
- %w(Unit\ tests test/unit),
65
- %w(Functional\ tests test/functional)
66
- ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
67
-
68
- desc "Report code statistics (KLOCs, etc) from the application"
69
- task :stats do
70
- #require 'extra/stats'
71
- verbose = true
72
- CodeStatistics.new(*STATS_DIRECTORIES).to_s
73
- end
74
-
75
61
  ##############################################################################
76
62
  # SVN
77
63
  ##############################################################################
@@ -79,4 +65,4 @@ end
79
65
  desc "Add new files to subversion"
80
66
  task :svn_add do
81
67
  system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
82
- end
68
+ end
@@ -0,0 +1 @@
1
+ <%= catch_content :layout %>
@@ -0,0 +1,23 @@
1
+ development:
2
+ adapter: mysql
3
+ database: sample_development
4
+ username: teh_user
5
+ password: secrets
6
+ host: localhost
7
+ socket: /tmp/mysql.sock
8
+
9
+ test:
10
+ adapter: mysql
11
+ database: sample_test
12
+ username: teh_user
13
+ password: secrets
14
+ host: localhost
15
+ socket: /tmp/mysql.sock
16
+
17
+ production:
18
+ adapter: mysql
19
+ database: sample_production
20
+ username: teh_user
21
+ password: secrets
22
+ host: /tmp/mysql.sock
23
+
@@ -0,0 +1 @@
1
+ puts "Loaded DEVELOPMENT Environment..."
@@ -0,0 +1 @@
1
+ puts "Loaded PRODUCTION Environment..."
@@ -0,0 +1 @@
1
+ puts "Loaded TEST Environment..."
@@ -1,53 +1,57 @@
1
1
  ---
2
- # hostname or IP to bind to.
2
+ # Hostname or IP address to bind to.
3
3
  :host: 127.0.0.1
4
4
 
5
- # port merb runs on or starting port for merb cluster.
5
+ # Port merb runs on or starting port for merb cluster.
6
6
  :port: "4000"
7
7
 
8
- # in development mode your controler classes get reloaded every request
9
- # and templates are parsed each time and not cached
10
- # in production mode templates are cached, as well as all your classes
8
+ # In development mode your controller classes get reloaded on every request,
9
+ # and templates are parsed each time and not cached. In production mode
10
+ # templates are cached, as well as all your classes
11
11
  :environment: development
12
12
 
13
- # uncomment for memory sessions. This only works when
14
- # you are running 1 merb at a time. ANd sessions do not persist
15
- # between restarts.
13
+ # Uncomment for memory sessions. This only works when you are running 1 merb
14
+ # at a time. And sessions do not persist between restarts.
16
15
  # :memory_session: true
17
16
 
18
- # This turns on the ActiveRecord sessions with rails parasite
19
- # mode if active_support gem is installed. Skeleton app comes with a
20
- # migration to create the sessions table. Or you can point merb to
21
- # the same sessions table that your rails app uses to share sessions
22
- # between merb and rails.
17
+ # Turn on memcached sessions.
18
+ # Requires these lines in merb_init.rb (and a running memcached server):
19
+ # require 'memcache_util'
20
+ # CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
21
+ # :mem_cache_session: true
22
+
23
+ # This turns on the ActiveRecord sessions with rails parasite mode if
24
+ # active_support gem is installed. Skeleton app comes with a migration to
25
+ # create the sessions table. Or you can point merb to the same sessions
26
+ # table that your rails app uses to share sessions between merb and rails.
23
27
  :sql_session: true
28
+ :log_level: debug
24
29
 
25
- # uncomment to use the merb upload progress
30
+ # Uncomment to use the merb upload progress
26
31
  #:config: dist/conf/upload.conf
27
32
 
28
- # uncomment to cache templates in dev mode.
29
- # templates are cached automatically in production mode.
33
+ # Uncomment to cache templates in dev mode. Templates are cached
34
+ # automatically in production mode.
30
35
  #:cache_templates: true
31
36
 
32
- # uncomment and set this is you want to run a drb
33
- # server for upload progress or other drb services.
37
+ # Uncomment and set this if you want to run a drb server for upload progress
38
+ # or other drb services.
34
39
  #:drb_server_port: 32323
35
40
 
36
- # If you want to protect some or all of your app with
37
- # HTTP basic auth then uncomment the folowing and fill
38
- # in your credentials you want it to use. Then you need
39
- # to set a before filter in a controller:
40
- # before :basic_authentication
41
+ # If you want to protect some or all of your app with HTTP basic auth then
42
+ # uncomment the following and fill in your credentials you want it to use.
43
+ # You will then need to set a 'before' filter in a controller. For example:
44
+ # before :basic_authentication
41
45
  #:basic_auth:
42
46
  # :username: ezra
43
47
  # :password: test
44
48
  # :domain: localhost
45
49
 
46
- # uncomment this if you want merb to daemonize when you start it
47
- # you can also just use merb -d for the same effect. Don't uncomment
48
- # this if you use the cluster option
50
+ # Uncomment this if you want merb to daemonize when you start it. You can also
51
+ # just use merb -d for the same effect. Don't uncomment this if you use the
52
+ # cluster option.
49
53
  #:daemonize: true
50
54
 
51
- # uncomment this to set the number of members in your merb cluster
52
- # don't set this and :daemonize: at the same time.
55
+ # Uncomment this to set the number of members in your merb cluster. Don't set
56
+ # this and :daemonize: at the same time.
53
57
  #:cluster: 3
@@ -2,20 +2,23 @@ puts "merb init called"
2
2
  require 'active_record'
3
3
  ActiveRecord::Base.verification_timeout = 14400
4
4
  ActiveRecord::Base.logger = MERB_LOGGER
5
- Dir[DIST_ROOT+"/app/helpers/*.rb"].each { |m| require m }
5
+
6
6
  require DIST_ROOT+"/app/controllers/application.rb"
7
- Dir[DIST_ROOT+"/app/controllers/*.rb"].each { |m| require m }
8
- Dir[DIST_ROOT+"/app/models/*.rb"].each { |m| require m }
9
- Dir[DIST_ROOT+"/lib/**/*.rb"].each { |m| require m }
10
- Dir[DIST_ROOT+"/plugins/*/init.rb"].each { |m| require m }
7
+ Dir[DIST_ROOT+"/app/controllers/*.rb"].each{ |m| require m }
8
+ Dir[DIST_ROOT+"/app/helpers/*.rb"].each { |m| require m }
9
+ Dir[DIST_ROOT+"/app/models/*.rb"].each { |m| require m }
10
+ Dir[DIST_ROOT+"/app/mailers/*.rb"].each { |m| require m }
11
+ Dir[DIST_ROOT+"/lib/*/lib/*.rb"].each { |m| require m }
12
+ Dir[DIST_ROOT+"/lib/*/bin/*.rb"].each { |m| require m }
13
+ Dir[DIST_ROOT+"/plugins/*/init.rb"].each { |m| require m }
14
+
15
+ #Get Database Config
16
+ puts "Connecting to database..."
17
+ conn_options = YAML::load(Erubis::Eruby.new(IO.read("#{DIST_ROOT}/conf/database.yml")).result)
18
+ ActiveRecord::Base.establish_connection conn_options["#{MERB_ENV}"]
11
19
 
12
- # set your db info here
13
- ActiveRecord::Base.establish_connection(
14
- :adapter => 'mysql',
15
- :username => 'root',
16
- :password => 'xxxxx',
17
- :database => 'merb'
18
- )
20
+ #Get Environment File
21
+ require "#{DIST_ROOT}/conf/environments/#{MERB_ENV}"
19
22
 
20
23
  # add your own ruby code here for app specific stuff. This file gets loaded
21
- # after the framework is loaded.
24
+ # after the framework is loaded.
@@ -1,13 +1,13 @@
1
1
  # Merb::RouteMatcher is the request routing mapper for the merb framework.
2
- # You can define placeholder parts of the url with the :smbol notation.
3
- # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
4
- # will match against a request to /foo/123/baz/456. It will then
5
- # use the class Bar as your merb controller and call the foo method on it.
6
- # the foo method will recieve a hash with {:bar => '123', :id => '456'}
7
- # as the content. So the :placeholders sections of your routes become
8
- # a hash of arguments to your controller methods.
9
- # The default route is installed
10
-
2
+ # You can define placeholder parts of the url with the :symbol notation. For
3
+ # example:
4
+ #
5
+ # r.add '/admin/:email/users/:id', :controller => 'admin_users', :action => 'foo'
6
+ #
7
+ # will match against a request to /admin/me@gmail.com/users/456. It will then
8
+ # use the class AdminUsers as your merb controller and call the 'foo' method
9
+ # on it. The 'foo' method will be able to access the :email and :id values via
10
+ # the 'params' hash, e.g. 'params[:email]' will return 'me@gmail.com'.
11
11
 
12
12
  puts "Compiling routes.."
13
13
  Merb::Router.prepare do |r|
@@ -1,8 +1,8 @@
1
1
  class AddSessionsTable < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :sessions, :force => true do |t|
4
- t.column :session_id, :string, :limit => 32
5
- t.column :created_at, :datetime
4
+ t.column :session_id, :string, :limit => 32
5
+ t.column :created_at, :datetime
6
6
  t.column :data, :text
7
7
  end
8
8
  add_index "sessions", ["session_id"], :name => "session_id_index"
@@ -15,7 +15,7 @@ require 'json'
15
15
 
16
16
 
17
17
  module Merb
18
- VERSION='0.3.4' unless defined?VERSION
18
+ VERSION='0.3.7' unless defined?VERSION
19
19
  class Server
20
20
  class << self
21
21
  def config
@@ -50,7 +50,7 @@ MERB_FRAMEWORK_ROOT = __DIR__
50
50
 
51
51
  MERB_ROOT = Merb::Server.merb_root || Dir.pwd
52
52
  DIST_ROOT = Merb::Server.dist_root || Dir.pwd+'/dist'
53
- MERB_ENV = Merb::Server.config[:environment]
53
+ MERB_ENV = Merb::Server.config[:environment].nil? ? 'development' : Merb::Server.config[:environment]
54
54
  MERB_VIEW_ROOT = MERB_ROOT / "dist/app/views"
55
55
 
56
56
  logpath = $TESTING ? "/tmp/merb_test.log" : "#{MERB_ROOT}/log/merb.#{Merb::Server.port}.log"
@@ -72,16 +72,6 @@ MERB_LOGGER.level = case (Merb::Server.log_level.downcase rescue '')
72
72
  else
73
73
  Logger::INFO
74
74
  end
75
-
76
- module Mongrel::Const
77
- HTTP_COOKIE = 'HTTP_COOKIE'.freeze
78
- QUERY_STRING = 'QUERY_STRING'.freeze
79
- APPLICATION_JSON = 'application/json'.freeze
80
- TEXT_JSON = 'text/x-json'.freeze
81
- APPLICATION_XML = 'application/xml'.freeze
82
- TEXT_XML = 'text/xml'.freeze
83
- UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
84
- end
85
75
 
86
76
  lib = File.join(__DIR__, 'merb')
87
77
  Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
@@ -89,9 +79,10 @@ Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require Fil
89
79
  require File.join(__DIR__, 'merb/vendor/paginator/paginator')
90
80
 
91
81
  class Merb::Controller
82
+ lib = File.join(__DIR__, 'merb')
92
83
  if Merb::Server.memory_session
93
- require "merb/session/merb_memory_session"
94
- Merb::MemorySession.setup
84
+ require lib + "/session/merb_memory_session"
85
+ Merb::MemorySessionContainer.setup
95
86
  include ::Merb::SessionMixin
96
87
  puts "memory session mixed in"
97
88
  end
@@ -104,14 +95,28 @@ class Merb::Controller
104
95
  rescue LoadError
105
96
  puts "Rails session compatibilty disabled. If you need this then install the actionpack gem"
106
97
  end
107
- require "merb/session/merb_ar_session"
98
+ require lib + "/session/merb_ar_session"
99
+ include ::Merb::SessionMixin
100
+ end
101
+
102
+ if Merb::Server.mem_cache_session
103
+ require lib + "/session/merb_mem_cache_session"
108
104
  include ::Merb::SessionMixin
109
- Thread.new{ loop{ sleep(60*60); ActiveRecord::Base.verify_active_connections! } }.priority = -10
105
+ puts "MemCache session mixed in"
110
106
  end
111
107
 
112
108
  if Merb::Server.basic_auth
113
- require "merb/mixins/basic_authentication_mixin"
109
+ require lib + "/mixins/basic_authentication_mixin"
114
110
  include ::Merb::Authentication
115
111
  puts "Basic Authentication mixed in"
116
112
  end
117
- end
113
+ end
114
+
115
+ if defined? ActiveRecord
116
+ Thread.new{ loop{ sleep(60*60); ActiveRecord::Base.verify_active_connections! } }.priority = -10
117
+ end
118
+
119
+ if $TESTING
120
+ test_files = File.join(lib, 'test', '*.rb')
121
+ Dir[test_files].each { |file| require file }
122
+ end
@@ -20,17 +20,13 @@ module Merb
20
20
  end
21
21
 
22
22
  def determine_cache_store
23
- case ::Merb::Server.cache_store
24
- when "file", :file
23
+ if ::Merb::Server.cache_store.to_s == "file"
25
24
  require 'merb/caching/store/file_cache'
26
25
  ::Merb::Caching::Store::FileCache.new
27
- when "memory", :memory
28
- require 'merb/caching/store/memory_cache'
29
- ::Merb::Caching::Store::MemoryCache.new
30
26
  else
31
27
  require 'merb/caching/store/memory_cache'
32
- ::Merb::Caching::Store::MemoryCache.new
33
- end
28
+ ::Merb::Caching::Store::MemoryCache.new
29
+ end
34
30
  end
35
31
  end
36
32
  end
@@ -0,0 +1,20 @@
1
+ module Merb
2
+ module Caching
3
+ module MemcachedStore
4
+
5
+
6
+ def get(name)
7
+ ::Cache.get("fragment:#{name}")
8
+ end
9
+
10
+ def put(name, content = nil)
11
+ ::Cache.put("fragment:#{name}", content)
12
+ content
13
+ end
14
+
15
+ def expire_fragment(name)
16
+ ::Cache.delete(name)
17
+ end
18
+ end
19
+ end
20
+ end
File without changes
@@ -1,12 +1,52 @@
1
- # Retain for backward compatibility. Methods are now included in Class.
2
- module ClassInheritableAttributes # :nodoc:
3
- end
4
-
5
1
  # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
6
2
  # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
7
3
  # to, for example, an array without those additions being shared with either their parent, siblings, or
8
4
  # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
9
5
  class Class # :nodoc:
6
+ def cattr_reader(*syms)
7
+ syms.flatten.each do |sym|
8
+ next if sym.is_a?(Hash)
9
+ class_eval(<<-EOS, __FILE__, __LINE__)
10
+ unless defined? @@#{sym}
11
+ @@#{sym} = nil
12
+ end
13
+
14
+ def self.#{sym}
15
+ @@#{sym}
16
+ end
17
+
18
+ def #{sym}
19
+ @@#{sym}
20
+ end
21
+ EOS
22
+ end
23
+ end
24
+
25
+ def cattr_writer(*syms)
26
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
27
+ syms.flatten.each do |sym|
28
+ class_eval(<<-EOS, __FILE__, __LINE__)
29
+ unless defined? @@#{sym}
30
+ @@#{sym} = nil
31
+ end
32
+
33
+ def self.#{sym}=(obj)
34
+ @@#{sym} = obj
35
+ end
36
+
37
+ #{"
38
+ def #{sym}=(obj)
39
+ @@#{sym} = obj
40
+ end
41
+ " unless options[:instance_writer] == false }
42
+ EOS
43
+ end
44
+ end
45
+
46
+ def cattr_accessor(*syms)
47
+ cattr_reader(*syms)
48
+ cattr_writer(*syms)
49
+ end
10
50
  def class_inheritable_reader(*syms)
11
51
  syms.each do |sym|
12
52
  next if sym.is_a?(Hash)