objectified_environments 1.0.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.
@@ -0,0 +1,206 @@
1
+ require File.join(File.dirname(__FILE__), 'objectified_environments/rails_requirer')
2
+
3
+ require 'rails/generators'
4
+
5
+ class ObjectifiedEnvironmentsGenerator < Rails::Generators::Base
6
+ def create_environment_files
7
+ needed_environments = all_environments.select { |e| (! environment_defined?(e)) }
8
+
9
+ needed_environments.each do |environment|
10
+ create_environment(environment)
11
+ end
12
+
13
+ create_superclasses_for(needed_environments)
14
+ end
15
+
16
+ private
17
+ def create_environment(environment)
18
+ target_file = File.join(objenv_dir, "#{environment.underscore}.rb")
19
+
20
+ if File.exist?(target_file)
21
+ puts "skip #{target_file}"
22
+ else
23
+ create_file(target_file) do
24
+ content_for_environment(environment)
25
+ end
26
+ end
27
+ end
28
+
29
+ def create_superclasses_for(environments)
30
+ superclasses = environments.map { |e| superclass_for_environment(e) }.uniq
31
+ superclasses -= [ "ObjectifiedEnvironments::Base" ]
32
+ superclasses = superclasses.map do |sc|
33
+ if sc =~ /::([A-Z0-9_]+)$/i
34
+ $1
35
+ else
36
+ sc
37
+ end
38
+ end
39
+
40
+ unless superclasses.length == 0
41
+ superclasses.each { |sc| create_environment(sc) }
42
+ create_superclasses_for(superclasses)
43
+ end
44
+ end
45
+
46
+ DEFAULT_ENVIRONMENT_SUPERCLASS = 'Objenv::Environment'
47
+ ENVIRONMENT_HIERARCHY = {
48
+ 'Environment' => "ObjectifiedEnvironments::Base",
49
+
50
+ 'LocalEnvironment' => 'Environment',
51
+ 'Development' => 'LocalEnvironment',
52
+ 'Test' => 'LocalEnvironment',
53
+
54
+ 'ProductionEnvironment' => 'Environment',
55
+ 'Production' => 'ProductionEnvironment'
56
+ }
57
+ ENVIRONMENTS_MODULE = "Objenv"
58
+
59
+ CLASS_COMMENTS = { }
60
+ CLASS_COMMENTS['Environment'] = <<-EOS
61
+ # This is the root class of all of your objectified environments -- i.e., all the other
62
+ # classes in config/lib/objenv. As such, it defines the behavior of a "default" environment,
63
+ # with subclasses overriding its methods as needed.
64
+ #
65
+ # You might think that this is useless -- after all, the whole point of environments is to
66
+ # define things that *aren't* common across RAILS_ENV settings, so what could possibly go
67
+ # here? However, you'll find that several things are useful about this class:
68
+ #
69
+ # - If you define all methods here and simply call #must_implement (defined in
70
+ # ObjectifiedEnvironments::Base) in them, then you'll have an elegant template that
71
+ # immediately shows everybody exactly what varies based on environment and what they
72
+ # need to think about/override when creating a new environment.
73
+ #
74
+ # - Often, you'll want to define methods on the environment that actually include a
75
+ # little bit of functionality -- like creating and returning a Memcached client, for
76
+ # example. This can therefore be a nice place to put the structure of those methods.
77
+ # (The method that the rest of the code actually calls directly can be defined here,
78
+ # while subclasses override methods that provide crucial information -- like the name
79
+ # of the Memcached host, for example -- that this method uses to do its work.)
80
+ #
81
+ # - You may well have cases where nearly all environments behave identically, but only
82
+ # one or two vary. It therefore makes a lot of sense to put the default behavior
83
+ # here, and override as necessary only in those one or two cases.
84
+ #
85
+ # Note that the inheritance hierarchy here is only suggested and part of the
86
+ # objectified_environments generator, but not the core Gem code itself. You can change and
87
+ # rearrange things to your heart's content.
88
+ EOS
89
+
90
+ CLASS_COMMENTS['LocalEnvironment'] = <<-EOS
91
+ # This class is a descendent of the base Environment class and is inherited by Development
92
+ # and Test, the two environments that typically run locally (i.e., not on a remote server).
93
+ # It's very common that these two environments share a lot of settings in common -- hence
94
+ # the case for this class.
95
+ #
96
+ # Note that the inheritance hierarchy here is only suggested and part of the
97
+ # objectified_environments generator, but not the core Gem code itself. You can change and
98
+ # rearrange things to your heart's content.
99
+ EOS
100
+
101
+ CLASS_COMMENTS['Development'] = <<-EOS
102
+ # This class defines environment settings when RAILS_ENV == 'development'.
103
+ #
104
+ # If you have settings to define that also apply to Test, please consider placing them
105
+ # in LocalEnvironment, the superclass of this class, instead. You'll keep your code DRYer.
106
+ #
107
+ # Note that the inheritance hierarchy here is only suggested and part of the
108
+ # objectified_environments generator, but not the core Gem code itself. You can change and
109
+ # rearrange things to your heart's content.
110
+ EOS
111
+
112
+ CLASS_COMMENTS['Test'] = <<-EOS
113
+ # This class defines environment settings when RAILS_ENV == 'test'.
114
+ #
115
+ # If you have settings to define that also apply to Development, please consider placing them
116
+ # in LocalEnvironment, the superclass of this class, instead. You'll keep your code DRYer.
117
+ #
118
+ # Note that the inheritance hierarchy here is only suggested and part of the
119
+ # objectified_environments generator, but not the core Gem code itself. You can change and
120
+ # rearrange things to your heart's content.
121
+ EOS
122
+
123
+ CLASS_COMMENTS['ProductionEnvironment'] = <<-EOS
124
+ # This class is a descendent of the base Environment class and is inherited by Production.
125
+ # While this may seem a bit silly -- why have an intermediate class between Environment and
126
+ # Production at all? -- there's a good reason for it. It's exceptionally common to create
127
+ # 'production-like' environments, like 'qa', 'beta', and so on. These environments very often
128
+ # share a great number of environment settings, and this class is a perfect place to put
129
+ # that code. If you start from the beginning separating out settings that apply to "any
130
+ # environment that's like production" from "only production itself", adding these environments
131
+ # in the future gets to be a whole lot easier.
132
+ #
133
+ # Note that the inheritance hierarchy here is only suggested and part of the
134
+ # objectified_environments generator, but not the core Gem code itself. You can change and
135
+ # rearrange things to your heart's content.
136
+ EOS
137
+
138
+ CLASS_COMMENTS['Production'] = <<-EOS
139
+ # This class defines environment settings when RAILS_ENV == 'production'.
140
+ #
141
+ # Please see the comment at the top of ProductionEnvironment, as well. If you have settings
142
+ # that apply to 'any production-like environment' rather than 'only production itself', putting
143
+ # that code in ProductionEnvironment instead of in this class will likely serve you better
144
+ # in the long run.
145
+ #
146
+ # Note that the inheritance hierarchy here is only suggested and part of the
147
+ # objectified_environments generator, but not the core Gem code itself. You can change and
148
+ # rearrange things to your heart's content.
149
+ EOS
150
+
151
+ def content_for_environment(environment)
152
+ %{#{CLASS_COMMENTS[environment.camelize] || ''}
153
+ module #{ENVIRONMENTS_MODULE}
154
+ class #{environment.camelize} < #{superclass_for_environment(environment)}
155
+ # Add your own method definitions here!
156
+ end
157
+ end
158
+ }
159
+ end
160
+
161
+ def superclass_for_environment(environment)
162
+ ENVIRONMENT_HIERARCHY[environment.camelize] || DEFAULT_ENVIRONMENT_SUPERCLASS
163
+ end
164
+
165
+ def environment_defined?(environment)
166
+ target_class_name = "#{ENVIRONMENTS_MODULE}::#{environment.camelize}"
167
+ begin
168
+ target_class_name.constantize
169
+ true
170
+ rescue NameError => ne
171
+ false
172
+ end
173
+ end
174
+
175
+ def all_environments
176
+ config_environments | [ current_environment ]
177
+ end
178
+
179
+ def config_environments
180
+ return [ ] unless File.directory?(config_environments_dir)
181
+
182
+ Dir.entries(config_environments_dir).map do |entry|
183
+ $1.downcase if entry =~ /^([A-Z0-9_]+)\.rb$/i
184
+ end.compact
185
+ end
186
+
187
+ def config_dir
188
+ @config_dir ||= File.expand_path(File.join(Rails.root, 'config'))
189
+ end
190
+
191
+ def config_environments_dir
192
+ @config_environments_dir ||= File.join(config_dir, 'environments')
193
+ end
194
+
195
+ def config_lib_dir
196
+ @config_lib_dir ||= File.join(config_dir, 'lib')
197
+ end
198
+
199
+ def objenv_dir
200
+ @objenv_dir ||= File.join(config_lib_dir, ENVIRONMENTS_MODULE.underscore)
201
+ end
202
+
203
+ def current_environment
204
+ Rails.env
205
+ end
206
+ end
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'objectified_environments/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "objectified_environments"
8
+ spec.version = ObjectifiedEnvironments::VERSION
9
+ spec.authors = ["Andrew Geweke"]
10
+ spec.email = ["andrew@geweke.org"]
11
+ spec.description = %q{Exposes your Rails.env as an object you can invoke methods on, use inheritance to structure, and otherwise use all modern powerful programming techniques on. In large projects, this can make an enormous difference in maintainability and reliability.}
12
+ spec.summary = %q{Vastly improve maintainability of your Rails.env-dependent code by using object-oriented environments.}
13
+ spec.homepage = "https://github.com/ageweke/objectified_environments"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+
25
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
26
+ spec.add_development_dependency "activerecord-jdbcsqlite3-adapter"
27
+ else
28
+ spec.add_development_dependency "sqlite3"
29
+ end
30
+
31
+ rails_version = ENV['OBJECTIFIED_ENVIRONMENTS_RAILS_TEST_VERSION']
32
+ rails_version = rails_version.strip if rails_version
33
+
34
+ version_spec = case rails_version
35
+ when nil then [ ">= 3.0", "<= 4.99.99" ]
36
+ when 'master' then nil# { :git => 'git://github.com/rails/rails.git' }
37
+ else [ "=#{rails_version}" ]
38
+ end
39
+
40
+ if version_spec
41
+ spec.add_dependency("rails", *version_spec)
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ module ObjectifiedEnvironments
2
+ module Specs
3
+ module Helpers
4
+ module CommandHelpers
5
+ def safe_system(command, options = { })
6
+ # Ugh. Bundler sets environment variables that causes subprocesses to use the same Bundler Gemfiles, etc.
7
+ # While I'm sure this is exactly what you want in most circumstances, it breaks our handling of different
8
+ # Rails versions. So we remove these explicitly when we execute a subprocess.
9
+ old_env = { }
10
+ ENV.keys.select { |k| k =~ /^BUNDLE_/ }.each do |key|
11
+ old_env[key] = ENV[key]
12
+ ENV.delete(key)
13
+ end
14
+
15
+ output = `#{command} 2>&1`
16
+
17
+ old_env.each { |k,v| ENV[k] = v }
18
+
19
+ successful = $?.success?
20
+ successful = false if options[:output_must_match] && (! (output =~ options[:output_must_match]))
21
+
22
+ unless successful
23
+ what_we_were_doing = options[:what_we_were_doing] || "run a command"
24
+
25
+ raise <<-EOS
26
+ Unable to #{what_we_were_doing}. In this directory:
27
+
28
+ #{Dir.pwd}
29
+
30
+ ...we ran:
31
+
32
+ #{command}
33
+
34
+ ...but it returned this result: #{$?.inspect}
35
+ ...and gave this output:
36
+
37
+ #{output}
38
+ EOS
39
+ end
40
+
41
+ output
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,299 @@
1
+ require File.join(File.dirname(__FILE__), 'command_helpers')
2
+ require 'fileutils'
3
+
4
+ module ObjectifiedEnvironments
5
+ module Specs
6
+ module Helpers
7
+ class RailsHelper
8
+ include ObjectifiedEnvironments::Specs::Helpers::CommandHelpers
9
+
10
+ DEFAULT_RAILS_ENV = 'test'
11
+
12
+ def initialize(container_dir, options = { })
13
+ @container_dir = container_dir
14
+ @options = options
15
+
16
+ @root = nil
17
+ @version = nil
18
+ @running = false
19
+ @rails_env = (options[:rails_env] || DEFAULT_RAILS_ENV).to_s.strip
20
+
21
+ raise "This is not a valid Rails.env: #{@rails_env.inspect}" if @rails_env.length == 0
22
+ end
23
+
24
+ attr_reader :root, :version, :rails_env
25
+
26
+ def run!(&block)
27
+ begin
28
+ @running = true
29
+ preserve_state do
30
+ new_rails_installation!
31
+
32
+ Dir.chdir(root)
33
+ ENV['RAILS_ENV'] = rails_env
34
+
35
+ block.call(self)
36
+
37
+ FileUtils.rm_rf(File.dirname(root)) unless options[:always_keep_installation]
38
+ end
39
+ ensure
40
+ @running = false
41
+ end
42
+ end
43
+
44
+ def major_version
45
+ Integer($1) if version && version =~ /^(\d+)\./i
46
+ end
47
+
48
+ def major_and_minor_version
49
+ Float($1) if version && version =~ /^(\d+\.\d+)\./i
50
+ end
51
+
52
+ def run_script_command
53
+ if major_version <= 2
54
+ "ruby #{File.join('script', 'runner')}"
55
+ else
56
+ "rails runner"
57
+ end
58
+ end
59
+
60
+ def run_script!(script_path, *args)
61
+ opts = args.pop if args && args[-1] && args[-1].kind_of?(Hash)
62
+ opts ||= { }
63
+
64
+ must_be_running!
65
+ Dir.chdir(root) do
66
+ cmd = "bundle exec #{run_script_command} #{script_path}"
67
+ if args.length > 0
68
+ cmd << " "
69
+ cmd << args.join(" ")
70
+ end
71
+ safe_system(cmd, opts)
72
+ end
73
+ end
74
+
75
+ def run_as_script!(contents, opts = { })
76
+ script_file = opts[:script_name] || "temp_rails_script_#{rand(1_000_000)}"
77
+ script_file << ".rb" unless script_file =~ /\.rb$/i
78
+
79
+ File.open(script_file, 'w') { |f| f.puts contents }
80
+
81
+ run_script!(script_file, opts)
82
+ end
83
+
84
+ def run_generator(*args)
85
+ cmd = if major_version <= 2
86
+ "script/generate"
87
+ else
88
+ "rails generate"
89
+ end
90
+
91
+ cmd = "bundle exec #{cmd} #{args.join(" ")}"
92
+ safe_system(cmd)
93
+ end
94
+
95
+ def running?
96
+ !! @running
97
+ end
98
+
99
+ private
100
+ attr_reader :container_dir, :options
101
+
102
+ def must_be_running!
103
+ unless running?
104
+ raise "You can only call this while the Rails helper is running, and it isn't right now."
105
+ end
106
+ end
107
+
108
+ def project_name
109
+ options[:project_name] || 'rails_project'
110
+ end
111
+
112
+ def preserve_state(&block)
113
+ old_dir = Dir.pwd
114
+ old_rails_env = ENV['RAILS_ENV']
115
+
116
+ begin
117
+ block.call
118
+ ensure
119
+ Dir.chdir(old_dir)
120
+ ENV['RAILS_ENV'] = old_rails_env
121
+ end
122
+ end
123
+
124
+ def notify(string, &block)
125
+ $stdout << "[#{string}..."
126
+ $stdout.flush
127
+
128
+ block.call
129
+
130
+ $stdout << "]"
131
+ $stdout.flush
132
+ end
133
+
134
+ def create_rails_holder!
135
+ raise "No version yet?!?" unless version
136
+ out = File.join(container_dir, "rails-#{Time.now.strftime("%Y%m%d-%H%M%S")}-#{rand(1_000_000)}-#{version}")
137
+ FileUtils.mkdir_p(out)
138
+ out
139
+ end
140
+
141
+ def new_rails_installation!
142
+ fetch_rails_version!
143
+ rails_holder = create_rails_holder!
144
+
145
+ Dir.chdir(rails_holder)
146
+ create_rails_project!
147
+
148
+ @root = File.join(rails_holder, project_name)
149
+ Dir.chdir(root)
150
+
151
+ set_gemfile!
152
+ modify_database_yml_as_needed!
153
+ copy_environment_as_needed!
154
+ run_bundle_install!
155
+
156
+ check_installed_rails_version!
157
+ end
158
+
159
+ def set_gemfile!
160
+ # For reasons I don't understand at all, running 'bundle install' against our installed Rails instance
161
+ # absolutely refuses to install remote gems -- all it will do is use ones that have already been installed.
162
+ # And that means it can only safely use gems in the top-level Gemfile of whatever gem or other code we're
163
+ # running under.
164
+ #
165
+ # As a result, we overwrite the Gemfile here to contain only the reference to Rails itself, rather than
166
+ # the additional stuff that the default Rails Gemfile contains. This, despite the fact that the command_helper
167
+ # explicitly strips out all BUNDLE_* environment variables before executing a subcommand.
168
+ #
169
+ # This is unfortunate, because it would be safer to use all the default gems. If a subsequent contributor
170
+ # knows what's going on here and how to make it work, by all means, please do!
171
+
172
+
173
+ # We need to specify the version for Rails exactly like the outer Gemfile specifies it, and it might be
174
+ # from git (e.g., if we're testing against the master branch of Rails), so we can't just use #version in this
175
+ # class. Rather, we go grab the spec from Bundler.
176
+ rails_spec = Bundler.environment.specs.detect { |s| s.name == 'rails' }
177
+ raise "Can't find Bundler spec for 'rails'" unless rails_spec
178
+ rails_spec_version = case rails_spec.source
179
+ when Bundler::Source::Git then ":git => '#{rails_spec.source.uri}'"
180
+ else "'#{rails_spec.version}'"
181
+ end
182
+
183
+ gem_lines = [
184
+ "source 'http://rubygems.org'",
185
+ "gem 'rails', #{rails_spec_version}"
186
+ ]
187
+
188
+ # Rails >= 3.2 uses sqlite3 by default, and won't even boot by default unless you add that to your Gemfile.
189
+ if major_and_minor_version >= 3.2
190
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
191
+ gem_lines << "gem 'activerecord-jdbcsqlite3-adapter'"
192
+ else
193
+ gem_lines << "gem 'sqlite3'"
194
+ end
195
+ end
196
+
197
+ if gem_lines.length > 0
198
+ notify("adding required lines to Gemfile") do
199
+ File.open("Gemfile", "w") { |f| f.puts gem_lines.join("\n") }
200
+ end
201
+ end
202
+ end
203
+
204
+ # If we're using a RAILS_ENV other than one of the three defaults, then database.yml needs to contain
205
+ # configuration for that environment, even if we never actually touch the database, because certain Rails
206
+ # versions refuse to even start if it's not present.
207
+ def modify_database_yml_as_needed!
208
+ require 'yaml'
209
+
210
+ db_yaml_file = File.join('config', 'database.yml')
211
+ db_yaml = YAML.load_file(db_yaml_file)
212
+
213
+ unless db_yaml[rails_env]
214
+ notify("adding environment '#{rails_env}' to database.yml") do
215
+ test_content = db_yaml['test']
216
+ raise "No default database.yml entry for 'test'?!?" unless test_content
217
+
218
+ db_yaml[rails_env] = test_content.dup
219
+ new_yaml = YAML.dump(db_yaml)
220
+ # Get rid of the silly '---' line that YAML.dump puts at the start.
221
+ new_yaml = new_yaml.split("\n").map { |l| l unless l =~ /^\-+$/i }.compact.join("\n")
222
+ File.open(db_yaml_file, 'w') { |f| f.puts new_yaml }
223
+ end
224
+ end
225
+ end
226
+
227
+ # Similarly, if we're using a non-default RAILS_ENV setting, we need to make sure we have an environment
228
+ # file for it.
229
+ def copy_environment_as_needed!
230
+ env_directory = File.join('config', 'environments')
231
+ env_file = File.join(env_directory, "#{rails_env}.rb")
232
+
233
+ unless File.exist?(env_file)
234
+ test_env_file = File.join(env_directory, "test.rb")
235
+ raise "No test.rb file at: #{test_env_file}?!?" unless File.exist?(test_env_file)
236
+ FileUtils.cp(test_env_file, env_file)
237
+ end
238
+ end
239
+
240
+ def check_installed_rails_version!
241
+ notify "checking version of Rails in our new project" do
242
+ output = run_as_script!(%{puts "Rails version: " + Rails.version},
243
+ :script_name => "check_rails_version",
244
+ :output_must_match => /^\s*Rails\s+version\s*:\s*\S+\s*$/mi,
245
+ :what_we_were_doing => "running a small script to check the version of Rails we installed")
246
+
247
+ if output =~ /^\s*Rails\s+version\s*:\s*(\S+)\s*$/mi
248
+ installed_version = $1
249
+
250
+ unless installed_version == version
251
+ raise "Whoa: the Rails project we created is reporting itself as version '#{installed_version}', but 'rails --version' gave us '#{version}'. Something is horribly wrong."
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ def fetch_rails_version!
258
+ v = nil
259
+
260
+ notify "checking version of Rails we're using" do
261
+ version_text = safe_system("rails --version", :output_must_match => /^\s*Rails\s+(\d+\.\d+\.\d+)/i, :what_we_were_doing => "checking the version of Rails used by the 'rails' command")
262
+ v = if version_text =~ /^\s*Rails\s+(\d+\.\d+\.\d+)/i
263
+ $1
264
+ else
265
+ raise "Unable to determine version of Rails; we got: #{version_text.inspect}"
266
+ end
267
+
268
+ $stdout << v
269
+ $stdout.flush
270
+ end
271
+
272
+ @version = v
273
+ end
274
+
275
+ def create_rails_project_command
276
+ if major_version <= 2
277
+ "rails"
278
+ else
279
+ "rails new"
280
+ end
281
+ end
282
+
283
+ def create_rails_project!
284
+ notify "creating new Rails installation" do
285
+ safe_system("#{create_rails_project_command} #{project_name}",
286
+ :what_we_were_doing => 'create a Rails project for our spec',
287
+ :output_must_match => %r{create.*config/boot}mi)
288
+ end
289
+ end
290
+
291
+ def run_bundle_install!
292
+ notify "running 'bundle install'" do
293
+ safe_system("bundle install", :what_we_were_doing => "run 'bundle install' for our test Rails project")
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end