has_named_bootstraps 0.0.2 → 0.1.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.
data/README CHANGED
@@ -1,16 +1,15 @@
1
1
  = HasNamedBootstraps
2
2
 
3
- *This is alpha code. The API is subject (and almost certain) to change. It shouldn't hurt your computer or data, but if it does, I am not obligated to do anything, even feel sorry for you.*
3
+ This is alpha code. The API is subject (and almost certain) to change. It shouldn't hurt your computer or data, but if it does, I am not obligated to do anything, even feel sorry for you.
4
4
 
5
- has_named_bootstraps is a simple way to load bootstrapped records into class-level constants, and generate a list in another constant of all such bootstraps. This is handy for generating <select>s and writing certain kinds of tests.
5
+ has_named_bootstraps is a simple way to load bootstrapped records into class-level constants, making application-level data integrity easier. You can also generate a list in another constant of all such bootstraps, which is handy for generating <select>s and writing certain kinds of tests.
6
6
 
7
- Getting the bootstrapped data in beforehand is up to you. I like seed_fu (http://github.com/mbleigh/seed-fu) for this.
7
+ Getting the bootstrapped data in beforehand is up to you. I like seed_fu (http://github.com/mbleigh/seed-fu) for this personally. If has_named_bootstraps can't find a bootstrap, by default it'll raise an error. It's also possible to have it log a warning and continue, or continue silently.
8
8
 
9
9
  = Example
10
10
 
11
11
  class Department < ActiveRecord::Base
12
12
  has_named_bootstraps(
13
- :VALID_DEPARTMENTS, # name of the constant that'll hold the list of all bootstrap constants
14
13
  :R_AND_D => 'R&D',
15
14
  :MARKETING => 'Marketing',
16
15
  :HANDSOME_DEVILS => 'Web Development'
@@ -25,15 +24,37 @@ Getting the bootstrapped data in beforehand is up to you. I like seed_fu (http:
25
24
  => #<Department id: 2, name: "Marketing">
26
25
  >> Department::HANDSOME_DEVILS
27
26
  => #<Department id: 3, name: "Web Development">
28
- >> Department::VALID_DEPARTMENTS
29
- => [#<Department id: 1, name: "R&D">, #<Department id: 2, name: "Marketing">, #<Department id: 3, name: "Web Development">]
27
+
28
+ With a master list:
29
+
30
+ class Dog < ActiveRecord::Base
31
+ has_named_bootstraps(
32
+ {:FIDO => 'Fido', :ROVER => 'Rover', :SPOT => 'Spot'},
33
+ :master_list => :GOOD_DOGS
34
+ )
35
+ end
36
+
37
+ >> Dog.find(:all)
38
+ => [#<Dog id: 1, name: "Spot">, #<Dog id: 2, name: "Fido">, #<Dog id: 3, name: "Rover">]
39
+ >> Dog::GOOD_DOGS
40
+ => [#<Dog id: 1, name: "Spot">, #<Dog id: 2, name: "Fido">, #<Dog id: 3, name: "Rover">]
41
+
42
+ See HasNamedBootstraps::ClassMethods for other options.
30
43
 
31
44
  = TODO
32
45
 
33
- -make master constant list optional
34
- -allow caller to specify an attribute to use to find record (currently hardcoded to "name")
35
- -write tests for shoulda macros, document same
46
+ make should_have_named_bootstraps detect multiple missing bootstraps (not just the first)
47
+
48
+ write tests for shoulda macros, document same
49
+
50
+ Rails 3 compatibility
51
+
52
+ Markdown README for GitHub
36
53
 
37
54
  = License and copyright
38
55
 
39
56
  Copyright (c) 2010 Children's Hospital Boston, released under the GNU Lesser General Public License.
57
+
58
+ = Acknowledgements
59
+
60
+ Thanks to Wyatt Greene for feedback, Dan Croak for patches and Harold Gimenez for Markdown advice. Boston.rb can beat up your Ruby group.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
@@ -1,32 +1,108 @@
1
1
  module HasNamedBootstraps
2
+ class MissingBootstrapError < Exception; end
3
+
2
4
  def self.included(other)
3
5
  other.extend(ClassMethods)
4
6
  end
5
7
 
6
8
  module ClassMethods
7
- # For each key/value pair in constant_hash, look up the record with the value in the "name" attribute, and assign it to the constant denoted by the key. Assign a list of all such created constants to the constant named by valid_list_name.
9
+ # For each key/value pair in constant_hash, look up the record with the value in the "name" attribute (by default), and assign it to the constant denoted by the key.
8
10
  #
9
11
  # Example:
10
12
  #
11
13
  # class Department < ActiveRecord::Base
12
14
  # has_named_bootstraps(
13
- # :VALID_DEPARTMENTS,
14
15
  # :R_AND_D => 'R&D',
15
16
  # :MARKETING => 'Marketing',
16
17
  # :HANDSOME_DEVILS => 'Web Development'
17
18
  # )
18
19
  # end
19
20
  #
20
- # Department::R_AND_D, Department::MARKETING and Department::HANDSOME_DEVILS are now all defined and point to an ActiveRecord object. Deparment::VALID_DEPARTMENTS is a list of those constants.
21
+ # Department::R_AND_D, Department::MARKETING and Department::HANDSOME_DEVILS are now all defined and point to an ActiveRecord object.
22
+ #
23
+ # Valid options:
24
+ #
25
+ # +master_list+:: Assign to the given constant name a list of all constants that has_named_bootstraps creates. Example:
26
+ #
27
+ # class Dog < ActiveRecord::Base
28
+ # has_named_bootstraps(
29
+ # {:FIDO => 'Fido', :ROVER => 'Rover', :SPOT => 'Spot'},
30
+ # :master_list => :GOOD_DOGS
31
+ # )
32
+ # end
33
+ #
34
+ # Dog::FIDO, Dog::ROVER, and Dog::SPOT are now defined as above, but there's also another constant, Dog::GOOD_DOGS, which contains a list of those three dogs.
35
+ #
36
+ #
37
+ #
38
+ # +name_field+:: Look up bootstrapped constants by the specified field, rather than +name+. Example:
39
+ #
40
+ # class Part < ActiveRecord::Base
41
+ # has_named_bootstraps(
42
+ # {
43
+ # :BANANA_GRABBER => '423-GOB-127',
44
+ # :TURNIP_TWADDLER => 'ZOMG-6-5000',
45
+ # :MOLE_WHACKER => '520-23-17X'
46
+ # },
47
+ # :name_field => :serial_number
48
+ # )
49
+ # end
50
+ #
51
+ # >> Part.all
52
+ # => [#<Part id: 1, serial_number: "423-GOB-127">, #<Part id: 2, serial_number: "ZOMG-6-5000">, #<Part id: 3, serial_number: "520-23-17X">]
53
+ # >> Part::BANANA_GRABBER
54
+ # => #<Part id: 1, serial_number: "423-GOB-127">
55
+ #
56
+ #
57
+ #
58
+ #
59
+ # +handle_missing_bootstrap+:: What to do if an expected bootstrap doesn't exist. Valid values are:
60
+ #
61
+ # * +raise+ (default):: Raise a HasNamedBootstraps::MissingBootstrapError
62
+ # * +warn+:: Log a warning in the Rails log and leave the constant set to nil.
63
+ # * +silent+:: Leave the constant set to nil as in +warn+, but without any warning logged.
64
+
65
+ def has_named_bootstraps(constant_hash, options={})
66
+ name_field = options[:name_field] || :name
67
+
68
+ handle_missing_bootstrap_strategy = options[:handle_missing_bootstrap] || :raise
69
+ unless %w(raise warn silent).include?(handle_missing_bootstrap_strategy.to_s)
70
+ raise ArgumentError, "Invalid :handle_missing_bootstrap option \"#{handle_missing_bootstrap_strategy}\""
71
+ end
21
72
 
22
- def has_named_bootstraps(valid_list_name, constant_hash)
23
- valid_list = []
73
+ # If the master_list option isn't set, we're just going to throw this
74
+ # list away, but it's clearer to just check once at the end if it's set
75
+ # rather than check repeatedly.
76
+ master_list = []
24
77
 
25
78
  constant_hash.each do |constant_name, record_name|
26
- valid_list << self.const_set(constant_name, self.find_by_name(record_name).freeze)
79
+ bootstrapped_record = self.find(:first, :conditions => {name_field => record_name})
80
+
81
+ unless bootstrapped_record
82
+ handle_missing_bootstrap(handle_missing_bootstrap_strategy, name_field, record_name)
83
+ end
84
+
85
+ master_list << self.const_set(constant_name, bootstrapped_record.freeze)
27
86
  end
28
87
 
29
- self.const_set(valid_list_name, valid_list.freeze)
88
+ master_list_name = options[:master_list]
89
+ if master_list_name
90
+ self.const_set(master_list_name, master_list.freeze)
91
+ end
92
+ end
93
+
94
+ def handle_missing_bootstrap(handle_missing_bootstrap_strategy, name_field, record_name) #:nodoc:
95
+ error_message = "Couldn't find bootstrap #{self} with #{name_field} \"#{record_name}\""
96
+
97
+ case handle_missing_bootstrap_strategy
98
+ when :raise
99
+ raise MissingBootstrapError, error_message
100
+ when :warn
101
+ Rails.logger.warn error_message
102
+ when :silent
103
+ # Don't raise anything, don't log a warning, just let the constant be
104
+ # silently set to nil.
105
+ end
30
106
  end
31
107
  end
32
108
  end
@@ -1,47 +1,146 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class HasNamedBootstrapsTest < ActiveSupport::TestCase
4
- CONSTANT_HASH = {
4
+ DEPARTMENT_CONSTANT_HASH = {
5
5
  :R_AND_D => 'R&D',
6
6
  :MARKETING => 'Marketing',
7
7
  :HANDSOME_DEVILS => 'Web Development'
8
8
  }
9
9
 
10
- setup do
11
- CONSTANT_HASH.values.each do |department_name|
12
- Department.create!(:name => department_name)
10
+ DOG_CONSTANT_HASH = {
11
+ :FIDO => 'Fido',
12
+ :ROVER => 'Rover',
13
+ :SPOT => 'Spot'
14
+ }
15
+
16
+ PART_CONSTANT_HASH = {
17
+ :BANANA_GRABBER => '423-GOB-127',
18
+ :TURNIP_TWADDLER => 'ZOMG-6-5000',
19
+ :MOLE_WHACKER => '520-23-17X'
20
+ }
21
+
22
+ def self.create_records_from_hash_values(klass, hash, name_field_sym = :name)
23
+ klass.delete_all
24
+ hash.values.each do |record_name|
25
+ klass.create!(name_field_sym => record_name)
13
26
  end
27
+ end
28
+
29
+ setup do
30
+ create_records_from_hash_values(Department, DEPARTMENT_CONSTANT_HASH)
31
+ create_records_from_hash_values(Dog, DOG_CONSTANT_HASH)
32
+ create_records_from_hash_values(Part, PART_CONSTANT_HASH, :serial_number)
14
33
 
15
- Department.class_eval do
16
- include HasNamedBootstraps
17
-
18
- # Ignore "already initialized constant WHATEVER" warnings that we
19
- # would otherwise get due to load_named_bootstraps running before every
20
- # test, redefining constants as we go (which we want in this case)
21
-
22
- old_verbose = $VERBOSE
23
- $VERBOSE = nil
24
- has_named_bootstraps(
25
- :VALID_DEPARTMENTS,
26
- CONSTANT_HASH
27
- )
28
- $VERBOSE = old_verbose
34
+ quietly do
35
+ Department.class_eval do
36
+ has_named_bootstraps(DEPARTMENT_CONSTANT_HASH)
37
+ end
38
+
39
+ Dog.class_eval do
40
+ has_named_bootstraps(
41
+ DOG_CONSTANT_HASH,
42
+ :master_list => :GOOD_DOGS # who's a good dog?
43
+ )
44
+ end
45
+
46
+ Part.class_eval do
47
+ has_named_bootstraps(
48
+ PART_CONSTANT_HASH,
49
+ :name_field => :serial_number
50
+ )
51
+ end
29
52
  end
30
53
  end
31
54
 
32
55
  context "#has_named_bootstraps" do
33
56
  should "create each expected constant" do
34
- CONSTANT_HASH.each do |constant_name, department_name|
57
+ DEPARTMENT_CONSTANT_HASH.each do |constant_name, department_name|
35
58
  bootstrapped_constant = Department.const_get(constant_name)
36
- assert_equal department_name, bootstrapped_constant.name
59
+ expected_department = Department.find_by_name(department_name)
60
+
61
+ assert expected_department # ensure not nil
62
+ assert_equal expected_department, bootstrapped_constant
37
63
  end
38
64
  end
39
65
 
40
- should "add each expected constant to the list of valid constants" do
41
- expected_object_ids = CONSTANT_HASH.keys.map{|hash_symbol| Department.const_get(hash_symbol)}.map(&:id)
42
- actual_object_ids = Department::VALID_DEPARTMENTS.map(&:id)
66
+ context "when an expected bootstrap is missing" do
67
+ setup do
68
+ @bad_country_name = "Erewhon"
69
+ @bad_country_const_name = "EREWHON"
70
+ @bad_bootstrap_hash_string = "{'#{@bad_country_const_name}' => '#{@bad_country_name}'}"
43
71
 
44
- assert_same_elements expected_object_ids, actual_object_ids
72
+ stub(Rails.logger).warn
73
+ assert_nil Country.find_by_name(@bad_country_const_name)
74
+ end
75
+
76
+ should "raise a HasNamedBootstraps::MissingBootstrapError" do
77
+ assert_raise HasNamedBootstraps::MissingBootstrapError do
78
+ # Using stringwise form of class_eval so we can interpolate
79
+ # @bad_bootstrap_hash_string, which otherwise this wants to interpret
80
+ # as an instance variable of Department.
81
+
82
+ quietly do
83
+ Country.class_eval <<-END_CLASS_EVAL
84
+ has_named_bootstraps(#{@bad_bootstrap_hash_string})
85
+ END_CLASS_EVAL
86
+ end
87
+ end
88
+ end
89
+
90
+ context "when :handle_missing_bootstrap is set to :warn" do
91
+ setup do
92
+ quietly do
93
+ Country.class_eval <<-END_CLASS_EVAL
94
+ has_named_bootstraps(#{@bad_bootstrap_hash_string}, :handle_missing_bootstrap => :warn)
95
+ END_CLASS_EVAL
96
+ end
97
+ end
98
+
99
+ should "log a warning" do
100
+ assert_received(Rails.logger) {|logger| logger.warn(/#{@bad_country_name}/)}
101
+ end
102
+
103
+ should "leave that constant set to nil" do
104
+ assert_nil Country.const_get(@bad_country_const_name)
105
+ end
106
+ end
107
+
108
+ context "when :handle_missing_bootstrap is set to :silent" do
109
+ setup do
110
+ dont_allow(Rails.logger).warn
111
+
112
+ quietly do
113
+ Country.class_eval <<-END_CLASS_EVAL
114
+ has_named_bootstraps(#{@bad_bootstrap_hash_string}, :handle_missing_bootstrap => :silent)
115
+ END_CLASS_EVAL
116
+ end
117
+ end
118
+
119
+ should "leave that constant set to nil" do
120
+ assert_nil Country.const_get(@bad_country_const_name)
121
+ end
122
+ end
123
+ end
124
+
125
+ context "when the :master_list option is set" do
126
+ should "add each expected constant to the list of valid constants" do
127
+ expected_objects = DOG_CONSTANT_HASH.keys.map{|hash_symbol| Dog.const_get(hash_symbol)}
128
+ actual_objects = Dog::GOOD_DOGS
129
+
130
+ expected_objects.each {|expected_object| assert expected_object} # ensure not nil
131
+ assert_same_elements expected_objects, actual_objects
132
+ end
133
+ end
134
+
135
+ context "when the :name_field option is set" do
136
+ should "look up bootstrapped constants by that field" do
137
+ PART_CONSTANT_HASH.each do |constant_name, serial_number|
138
+ expected_constant = Part.find_by_serial_number(serial_number)
139
+ actual_constant = Part.const_get(constant_name)
140
+
141
+ assert_equal expected_constant, actual_constant
142
+ end
143
+ end
45
144
  end
46
145
  end
47
146
  end
@@ -4,7 +4,16 @@ require 'active_support/test_case'
4
4
  require 'active_record'
5
5
  require 'active_record/fixtures'
6
6
  require 'shoulda'
7
- require 'ruby-debug'
7
+
8
+ require 'rr'
9
+ class Test::Unit::TestCase
10
+ include RR::Adapters::TestUnit
11
+ end
12
+
13
+ begin
14
+ require 'ruby-debug'
15
+ rescue LoadError
16
+ end
8
17
 
9
18
  require 'lib/has_named_bootstraps'
10
19
 
@@ -17,7 +26,51 @@ ActiveRecord::Schema.define do
17
26
  create_table 'departments', :force => true do |t|
18
27
  t.column 'name', :string
19
28
  end
29
+
30
+ create_table 'dogs', :force => true do |t|
31
+ t.column 'name', :string
32
+ end
33
+
34
+ create_table 'parts', :force => true do |t|
35
+ t.column 'serial_number', :string
36
+ end
37
+
38
+ create_table 'countries', :force => true do |t|
39
+ t.column 'name', :string
40
+ end
20
41
  end
21
42
 
22
43
  class Department < ActiveRecord::Base
44
+ include HasNamedBootstraps
45
+ end
46
+
47
+ class Dog < ActiveRecord::Base
48
+ include HasNamedBootstraps
49
+ end
50
+
51
+ class Part < ActiveRecord::Base
52
+ include HasNamedBootstraps
53
+ end
54
+
55
+ class Country < ActiveRecord::Base
56
+ include HasNamedBootstraps
57
+ end
58
+
59
+ # We're going to be redefining a lot of constants in the tests, so supress
60
+ # warnings of the same.
61
+
62
+ def quietly
63
+ old_verbose = $VERBOSE
64
+ $VERBOSE = nil
65
+ yield
66
+ $VERBOSE = old_verbose
67
+ end
68
+
69
+ # Dummy Rails logger, for testing warnings.
70
+ module Rails
71
+ FAKE_LOGGER = Object.new
72
+
73
+ def self.logger
74
+ FAKE_LOGGER
75
+ end
23
76
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 2
10
- version: 0.0.2
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phil Darnowsky
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-05-28 00:00:00 -04:00
18
+ date: 2010-06-26 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency