doozer 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +57 -0
  5. data/Rakefile +62 -0
  6. data/VERSION +1 -0
  7. data/bin/doozer +6 -0
  8. data/doozer.gemspec +156 -0
  9. data/lib/doozer.rb +35 -0
  10. data/lib/doozer/active_support/array.rb +14 -0
  11. data/lib/doozer/active_support/class.rb +221 -0
  12. data/lib/doozer/active_support/date_time.rb +23 -0
  13. data/lib/doozer/active_support/object.rb +43 -0
  14. data/lib/doozer/active_support/time.rb +32 -0
  15. data/lib/doozer/app.rb +294 -0
  16. data/lib/doozer/configs.rb +146 -0
  17. data/lib/doozer/controller.rb +340 -0
  18. data/lib/doozer/exceptions.rb +12 -0
  19. data/lib/doozer/extend.rb +10 -0
  20. data/lib/doozer/initializer.rb +104 -0
  21. data/lib/doozer/lib.rb +32 -0
  22. data/lib/doozer/logger.rb +12 -0
  23. data/lib/doozer/orm/active_record.rb +28 -0
  24. data/lib/doozer/orm/data_mapper.rb +22 -0
  25. data/lib/doozer/orm/sequel.rb +21 -0
  26. data/lib/doozer/partial.rb +99 -0
  27. data/lib/doozer/plugins/paginate/init.rb +2 -0
  28. data/lib/doozer/plugins/paginate/lib/paginate.rb +32 -0
  29. data/lib/doozer/plugins/paginate/lib/paginate/collection.rb +60 -0
  30. data/lib/doozer/plugins/paginate/lib/paginate/finder.rb +116 -0
  31. data/lib/doozer/plugins/paginate/lib/paginate/view_helpers.rb +37 -0
  32. data/lib/doozer/rackup/server.ru +35 -0
  33. data/lib/doozer/rackup/test.rb +20 -0
  34. data/lib/doozer/redirect.rb +12 -0
  35. data/lib/doozer/route.rb +292 -0
  36. data/lib/doozer/scripts/cluster.rb +126 -0
  37. data/lib/doozer/scripts/console.rb +2 -0
  38. data/lib/doozer/scripts/migrate.rb +108 -0
  39. data/lib/doozer/scripts/task.rb +60 -0
  40. data/lib/doozer/scripts/test.rb +23 -0
  41. data/lib/doozer/version.rb +8 -0
  42. data/lib/doozer/view_helpers.rb +251 -0
  43. data/lib/doozer/watcher.rb +369 -0
  44. data/lib/generator/generator.rb +548 -0
  45. data/templates/skeleton/Rakefile +3 -0
  46. data/templates/skeleton/app/controllers/application_controller.rb +2 -0
  47. data/templates/skeleton/app/controllers/index_controller.rb +7 -0
  48. data/templates/skeleton/app/helpers/application_helper.rb +17 -0
  49. data/templates/skeleton/app/views/global/_header.html.erb +7 -0
  50. data/templates/skeleton/app/views/global/_navigation.html.erb +6 -0
  51. data/templates/skeleton/app/views/index/index.html.erb +108 -0
  52. data/templates/skeleton/app/views/layouts/default.html.erb +23 -0
  53. data/templates/skeleton/config/app.yml +31 -0
  54. data/templates/skeleton/config/boot.rb +17 -0
  55. data/templates/skeleton/config/database.yml +25 -0
  56. data/templates/skeleton/config/environment.rb +11 -0
  57. data/templates/skeleton/config/rack.rb +30 -0
  58. data/templates/skeleton/config/routes.rb +69 -0
  59. data/templates/skeleton/script/cluster +4 -0
  60. data/templates/skeleton/script/console +15 -0
  61. data/templates/skeleton/script/migrate +4 -0
  62. data/templates/skeleton/script/task +4 -0
  63. data/templates/skeleton/script/test +4 -0
  64. data/templates/skeleton/static/404.html +16 -0
  65. data/templates/skeleton/static/500.html +16 -0
  66. data/templates/skeleton/static/css/style.css +32 -0
  67. data/templates/skeleton/static/favicon.ico +0 -0
  68. data/templates/skeleton/static/js/application.js +1 -0
  69. data/templates/skeleton/static/js/jquery-1.3.min.js +19 -0
  70. data/templates/skeleton/static/robots.txt +5 -0
  71. data/templates/skeleton/test/fixtures/setup.rb +6 -0
  72. data/templates/skeleton/test/setup.rb +33 -0
  73. data/test/doozer_test.rb +7 -0
  74. data/test/project/Rakefile +3 -0
  75. data/test/project/app/controllers/application_controller.rb +2 -0
  76. data/test/project/app/controllers/index_controller.rb +7 -0
  77. data/test/project/app/helpers/application_helper.rb +17 -0
  78. data/test/project/app/views/global/_header.html.erb +7 -0
  79. data/test/project/app/views/global/_navigation.html.erb +6 -0
  80. data/test/project/app/views/index/index.html.erb +108 -0
  81. data/test/project/app/views/layouts/default.html.erb +23 -0
  82. data/test/project/config/app.yml +31 -0
  83. data/test/project/config/boot.rb +17 -0
  84. data/test/project/config/database.yml +25 -0
  85. data/test/project/config/environment.rb +11 -0
  86. data/test/project/config/rack.rb +30 -0
  87. data/test/project/config/routes.rb +72 -0
  88. data/test/project/script/cluster +4 -0
  89. data/test/project/script/console +15 -0
  90. data/test/project/script/migrate +4 -0
  91. data/test/project/script/task +4 -0
  92. data/test/project/script/test +4 -0
  93. data/test/project/static/404.html +16 -0
  94. data/test/project/static/500.html +16 -0
  95. data/test/project/static/css/style.css +32 -0
  96. data/test/project/static/favicon.ico +0 -0
  97. data/test/project/static/js/application.js +1 -0
  98. data/test/project/static/js/jquery-1.3.min.js +19 -0
  99. data/test/project/static/robots.txt +5 -0
  100. data/test/project/test/fixtures/setup.rb +6 -0
  101. data/test/project/test/setup.rb +33 -0
  102. data/test/routing_test.rb +66 -0
  103. data/test/test_helper.rb +26 -0
  104. metadata +169 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Greg Melton
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = What is Doozer?
2
+ * A lightweight, ORM agnostic, Rack (http://rack.rubyforge.org) compatible MVC framework.
3
+
4
+ == Project Inspiration
5
+ * Doozer was initially a project I started working on for Google App Engine. It was eventually abandoned since GAE lacked (at the time and may still) proper support for migrations. I ported some of the code to Ruby mainly to learn the internals of Rack back in December 2008.
6
+ * Rails conventions and methodologies (mostly the application structure, configuration, nomenclature, scripts, and a few viewhelper methods).
7
+ * Clustering ala Mongrel::Cluster
8
+
9
+ == Requirements
10
+ * Ruby < 1.9 (untestested on Ruby 1.9 as of now)
11
+ * Rack (http://rack.rubyforge.org) gem (0.9.1 or 1.0.0)
12
+ * Any http server supported by Rack
13
+ * ActiveRecord, DataMapper or Sequel (only if you need DB support)
14
+
15
+ == Getting Started
16
+ 1. Install the gem(s):
17
+ - gem sources -a http://gems.github.com (if you haven't already)
18
+ - sudo gem install rack
19
+ - sudo gem install grippy-doozer
20
+ 2. Run 'doozer test-app' to generate the base application skeleton.
21
+ 3. Fire up the application in development mode
22
+ - cd test-app
23
+ - script/cluster -C start
24
+ 4. Navigate to http://localhost:9292
25
+
26
+ == Configuration
27
+ * Doozer is configurable to use an ORM library of your choosing: ActiveRecord, DataMapper, or Sequel.
28
+ * By default, routes are handled by Doozer controllers or you can define other Rack compatible applications which handle specific request paths.
29
+ * Http server of your liking (Mongrel, Thin, Lighttpd, or anything supported by Rack).
30
+ * Multiple appservers.
31
+ * Static directories.
32
+
33
+ == Useful Scripts
34
+ * Generate an application skeleton. Run 'doozer test-app' (see Getting Started).
35
+ * Generate views, controllers, and models (depending on the configured ORM). Run 'doozer generate -h' for more info.
36
+ * start/stop/restart your web server(s). Run 'script/cluster -h' for more info.
37
+ * Migrations up or down. 'script/migrate -h'.
38
+ * Tasks. Run 'script/task -h' for more info
39
+ * There is a rudimentary test command which allows you to run your own test suite for your application. Run 'script/test -h' for more info.
40
+
41
+ == Current limitations:
42
+ * Doozer has no test suite. Tsk-tsk I know. In the works.
43
+ * Right now, it doesn't keep track of migration versions.
44
+ * Magic routes are turned off. Still debating adding them back.
45
+ * Not all the documentation is in place.
46
+
47
+ == Note on Patches/Pull Requests
48
+ * Fork the project.
49
+ * Make your feature addition or bug fix.
50
+ * Add tests for it. This is important so I don't break it in a
51
+ future version unintentionally.
52
+ * Commit, do not mess with rakefile, version, or history.
53
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
54
+ * Send me a pull request. Bonus points for topic branches.
55
+
56
+ == Copyright
57
+ Copyright (c) 2009 Greg Melton. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "doozer"
8
+ gem.summary = %Q{A little MVC framework for Rack applications.}
9
+ gem.description = %Q{This GEM provides a small, barebones framework for creating MVC Rack applications.}
10
+ gem.email = "gmelton@whorde.com"
11
+ gem.homepage = "http://github.com/grippy/doozer"
12
+ gem.authors = ["grippy"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ gem.files.include %w() # lib/**/**/**/**/** templates/**/**/**/**
15
+
16
+ end
17
+
18
+ Jeweler::GemcutterTasks.new do | gem |
19
+ end
20
+
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+
46
+
47
+
48
+ task :default => :test
49
+
50
+ require 'rake/rdoctask'
51
+ Rake::RDocTask.new do |rdoc|
52
+ if File.exist?('VERSION')
53
+ version = File.read('VERSION')
54
+ else
55
+ version = ""
56
+ end
57
+
58
+ rdoc.rdoc_dir = 'rdoc'
59
+ rdoc.title = "doozer #{version}"
60
+ rdoc.rdoc_files.include('README*')
61
+ rdoc.rdoc_files.include('lib/**/*.rb')
62
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/bin/doozer ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
+ require 'doozer'
6
+ Doozer::Generator.run(*ARGV)
data/doozer.gemspec ADDED
@@ -0,0 +1,156 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{doozer}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["grippy"]
12
+ s.date = %q{2009-10-22}
13
+ s.default_executable = %q{doozer}
14
+ s.description = %q{This GEM provides a small, barebones framework for creating MVC Rack applications.}
15
+ s.email = %q{gmelton@whorde.com}
16
+ s.executables = ["doozer"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/doozer",
29
+ "doozer.gemspec",
30
+ "lib/doozer.rb",
31
+ "lib/doozer/active_support/array.rb",
32
+ "lib/doozer/active_support/class.rb",
33
+ "lib/doozer/active_support/date_time.rb",
34
+ "lib/doozer/active_support/object.rb",
35
+ "lib/doozer/active_support/time.rb",
36
+ "lib/doozer/app.rb",
37
+ "lib/doozer/configs.rb",
38
+ "lib/doozer/controller.rb",
39
+ "lib/doozer/exceptions.rb",
40
+ "lib/doozer/extend.rb",
41
+ "lib/doozer/initializer.rb",
42
+ "lib/doozer/lib.rb",
43
+ "lib/doozer/logger.rb",
44
+ "lib/doozer/orm/active_record.rb",
45
+ "lib/doozer/orm/data_mapper.rb",
46
+ "lib/doozer/orm/sequel.rb",
47
+ "lib/doozer/partial.rb",
48
+ "lib/doozer/plugins/paginate/init.rb",
49
+ "lib/doozer/plugins/paginate/lib/paginate.rb",
50
+ "lib/doozer/plugins/paginate/lib/paginate/collection.rb",
51
+ "lib/doozer/plugins/paginate/lib/paginate/finder.rb",
52
+ "lib/doozer/plugins/paginate/lib/paginate/view_helpers.rb",
53
+ "lib/doozer/rackup/server.ru",
54
+ "lib/doozer/rackup/test.rb",
55
+ "lib/doozer/redirect.rb",
56
+ "lib/doozer/route.rb",
57
+ "lib/doozer/scripts/cluster.rb",
58
+ "lib/doozer/scripts/console.rb",
59
+ "lib/doozer/scripts/migrate.rb",
60
+ "lib/doozer/scripts/task.rb",
61
+ "lib/doozer/scripts/test.rb",
62
+ "lib/doozer/version.rb",
63
+ "lib/doozer/view_helpers.rb",
64
+ "lib/doozer/watcher.rb",
65
+ "lib/generator/generator.rb",
66
+ "templates/skeleton/Rakefile",
67
+ "templates/skeleton/app/controllers/application_controller.rb",
68
+ "templates/skeleton/app/controllers/index_controller.rb",
69
+ "templates/skeleton/app/helpers/application_helper.rb",
70
+ "templates/skeleton/app/views/global/_header.html.erb",
71
+ "templates/skeleton/app/views/global/_navigation.html.erb",
72
+ "templates/skeleton/app/views/index/index.html.erb",
73
+ "templates/skeleton/app/views/layouts/default.html.erb",
74
+ "templates/skeleton/config/app.yml",
75
+ "templates/skeleton/config/boot.rb",
76
+ "templates/skeleton/config/database.yml",
77
+ "templates/skeleton/config/environment.rb",
78
+ "templates/skeleton/config/rack.rb",
79
+ "templates/skeleton/config/routes.rb",
80
+ "templates/skeleton/script/cluster",
81
+ "templates/skeleton/script/console",
82
+ "templates/skeleton/script/migrate",
83
+ "templates/skeleton/script/task",
84
+ "templates/skeleton/script/test",
85
+ "templates/skeleton/static/404.html",
86
+ "templates/skeleton/static/500.html",
87
+ "templates/skeleton/static/css/style.css",
88
+ "templates/skeleton/static/favicon.ico",
89
+ "templates/skeleton/static/js/application.js",
90
+ "templates/skeleton/static/js/jquery-1.3.min.js",
91
+ "templates/skeleton/static/robots.txt",
92
+ "templates/skeleton/test/fixtures/setup.rb",
93
+ "templates/skeleton/test/setup.rb",
94
+ "test/doozer_test.rb",
95
+ "test/project/Rakefile",
96
+ "test/project/app/controllers/application_controller.rb",
97
+ "test/project/app/controllers/index_controller.rb",
98
+ "test/project/app/helpers/application_helper.rb",
99
+ "test/project/app/views/global/_header.html.erb",
100
+ "test/project/app/views/global/_navigation.html.erb",
101
+ "test/project/app/views/index/index.html.erb",
102
+ "test/project/app/views/layouts/default.html.erb",
103
+ "test/project/config/app.yml",
104
+ "test/project/config/boot.rb",
105
+ "test/project/config/database.yml",
106
+ "test/project/config/environment.rb",
107
+ "test/project/config/rack.rb",
108
+ "test/project/config/routes.rb",
109
+ "test/project/script/cluster",
110
+ "test/project/script/console",
111
+ "test/project/script/migrate",
112
+ "test/project/script/task",
113
+ "test/project/script/test",
114
+ "test/project/static/404.html",
115
+ "test/project/static/500.html",
116
+ "test/project/static/css/style.css",
117
+ "test/project/static/favicon.ico",
118
+ "test/project/static/js/application.js",
119
+ "test/project/static/js/jquery-1.3.min.js",
120
+ "test/project/static/robots.txt",
121
+ "test/project/test/fixtures/setup.rb",
122
+ "test/project/test/setup.rb",
123
+ "test/routing_test.rb",
124
+ "test/test_helper.rb"
125
+ ]
126
+ s.homepage = %q{http://github.com/grippy/doozer}
127
+ s.rdoc_options = ["--charset=UTF-8"]
128
+ s.require_paths = ["lib"]
129
+ s.rubygems_version = %q{1.3.5}
130
+ s.summary = %q{A little MVC framework for Rack applications.}
131
+ s.test_files = [
132
+ "test/doozer_test.rb",
133
+ "test/project/app/controllers/application_controller.rb",
134
+ "test/project/app/controllers/index_controller.rb",
135
+ "test/project/app/helpers/application_helper.rb",
136
+ "test/project/config/boot.rb",
137
+ "test/project/config/environment.rb",
138
+ "test/project/config/rack.rb",
139
+ "test/project/config/routes.rb",
140
+ "test/project/test/fixtures/setup.rb",
141
+ "test/project/test/setup.rb",
142
+ "test/routing_test.rb",
143
+ "test/test_helper.rb"
144
+ ]
145
+
146
+ if s.respond_to? :specification_version then
147
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
148
+ s.specification_version = 3
149
+
150
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
151
+ else
152
+ end
153
+ else
154
+ end
155
+ end
156
+
data/lib/doozer.rb ADDED
@@ -0,0 +1,35 @@
1
+ DOOZER_PATH = File.expand_path(File.dirname(__FILE__))
2
+ $:.unshift(DOOZER_PATH) unless $:.include?(DOOZER_PATH)
3
+
4
+ # load active_support hooks
5
+ require File.join(DOOZER_PATH, 'doozer/extend')
6
+
7
+ module Doozer
8
+
9
+ autoload :App, "doozer/app"
10
+ autoload :Configs, "doozer/configs"
11
+ autoload :Controller, "doozer/controller"
12
+ autoload :Initializer, "doozer/initializer"
13
+ autoload :Lib, "doozer/lib"
14
+ autoload :Partial, "doozer/partial"
15
+ autoload :Redirect, "doozer/redirect"
16
+
17
+ module Routing
18
+ autoload :Route, "doozer/route"
19
+ autoload :Routes, "doozer/route"
20
+ end
21
+
22
+ module Util
23
+ autoload :Logger, "doozer/logger"
24
+ end
25
+
26
+ module Exceptions
27
+ autoload :Route, "doozer/exceptions"
28
+ end
29
+
30
+ autoload :ViewHelpers, "doozer/view_helpers"
31
+ autoload :Version, "doozer/version"
32
+
33
+ # Generator methods
34
+ autoload :Generator, "generator/generator"
35
+ end
@@ -0,0 +1,14 @@
1
+ class Array
2
+ # Extracts options from a set of arguments. Removes and returns the last
3
+ # element in the array if it's a hash, otherwise returns a blank hash.
4
+ #
5
+ # def options(*args)
6
+ # args.extract_options!
7
+ # end
8
+ #
9
+ # options(1, 2) # => {}
10
+ # options(1, 2, :a => :b) # => {:a=>:b}
11
+ def extract_options!
12
+ last.is_a?(::Hash) ? pop : {}
13
+ end
14
+ end
@@ -0,0 +1,221 @@
1
+ # Retain for backward compatibility. Methods are now included in Class.
2
+ module ClassInheritableAttributes # :nodoc:
3
+ end
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, __FILE__, __LINE__ + 1)
14
+ def self.#{sym} # def self.after_add
15
+ read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
16
+ end # end
17
+
18
+ def #{sym} # def after_add
19
+ self.class.#{sym} # self.class.after_add
20
+ end # end
21
+ EOS
22
+ end
23
+ end
24
+
25
+ def class_inheritable_writer(*syms)
26
+ options = syms.extract_options!
27
+ syms.each do |sym|
28
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
29
+ def self.#{sym}=(obj) # def self.color=(obj)
30
+ write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj)
31
+ end # end
32
+ #
33
+ #{" #
34
+ def #{sym}=(obj) # def color=(obj)
35
+ self.class.#{sym} = obj # self.class.color = obj
36
+ end # end
37
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
38
+ EOS
39
+ end
40
+ end
41
+
42
+ def class_inheritable_array_writer(*syms)
43
+ options = syms.extract_options!
44
+ syms.each do |sym|
45
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
46
+ def self.#{sym}=(obj) # def self.levels=(obj)
47
+ write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj)
48
+ end # end
49
+ #
50
+ #{" #
51
+ def #{sym}=(obj) # def levels=(obj)
52
+ self.class.#{sym} = obj # self.class.levels = obj
53
+ end # end
54
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
55
+ EOS
56
+ end
57
+ end
58
+
59
+ def class_inheritable_hash_writer(*syms)
60
+ options = syms.extract_options!
61
+ syms.each do |sym|
62
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
63
+ def self.#{sym}=(obj) # def self.nicknames=(obj)
64
+ write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj)
65
+ end # end
66
+ #
67
+ #{" #
68
+ def #{sym}=(obj) # def nicknames=(obj)
69
+ self.class.#{sym} = obj # self.class.nicknames = obj
70
+ end # end
71
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
72
+ EOS
73
+ end
74
+ end
75
+
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)
89
+ end
90
+
91
+ def inheritable_attributes
92
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
93
+ end
94
+
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)
105
+ end
106
+
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]
114
+ end
115
+
116
+ def reset_inheritable_attributes
117
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
118
+ end
119
+
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)
126
+
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.duplicable? ? value.dup : value)
132
+ end
133
+ end
134
+
135
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
136
+ end
137
+
138
+ alias inherited_without_inheritable_attributes inherited
139
+ alias inherited inherited_with_inheritable_attributes
140
+ end
141
+
142
+ class Class
143
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
144
+ # each subclass has a copy of parent's attribute.
145
+ #
146
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
147
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
148
+ #
149
+ # @api public
150
+ #
151
+ # @todo Do we want to block instance_reader via :instance_reader => false
152
+ # @todo It would be preferable that we do something with a Hash passed in
153
+ # (error out or do the same as other methods above) instead of silently
154
+ # moving on). In particular, this makes the return value of this function
155
+ # less useful.
156
+ def extlib_inheritable_reader(*ivars)
157
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
158
+
159
+ ivars.each do |ivar|
160
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
161
+ def self.#{ivar}
162
+ return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
163
+ ivar = superclass.#{ivar}
164
+ return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
165
+ @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
166
+ end
167
+ RUBY
168
+ unless instance_reader == false
169
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
170
+ def #{ivar}
171
+ self.class.#{ivar}
172
+ end
173
+ RUBY
174
+ end
175
+ end
176
+ end
177
+
178
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
179
+ # each subclass has a copy of parent's attribute.
180
+ #
181
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
182
+ # define inheritable writer for.
183
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
184
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
185
+ #
186
+ # @api public
187
+ #
188
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
189
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
190
+ def extlib_inheritable_writer(*ivars)
191
+ instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
192
+ ivars.each do |ivar|
193
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
194
+ def self.#{ivar}=(obj)
195
+ @#{ivar} = obj
196
+ end
197
+ RUBY
198
+ unless instance_writer == false
199
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
200
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
201
+ RUBY
202
+ end
203
+
204
+ self.send("#{ivar}=", yield) if block_given?
205
+ end
206
+ end
207
+
208
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
209
+ # each subclass has a copy of parent's attribute.
210
+ #
211
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
212
+ # define inheritable accessor for.
213
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
214
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
215
+ #
216
+ # @api public
217
+ def extlib_inheritable_accessor(*syms, &block)
218
+ extlib_inheritable_reader(*syms)
219
+ extlib_inheritable_writer(*syms, &block)
220
+ end
221
+ end