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 +30 -9
- data/VERSION +1 -1
- data/lib/has_named_bootstraps.rb +83 -7
- data/test/test_has_named_bootstraps.rb +123 -24
- data/test/test_helper.rb +54 -1
- metadata +3 -3
data/README
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
= HasNamedBootstraps
|
|
2
2
|
|
|
3
|
-
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
1
|
+
0.1.0
|
data/lib/has_named_bootstraps.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
57
|
+
DEPARTMENT_CONSTANT_HASH.each do |constant_name, department_name|
|
|
35
58
|
bootstrapped_constant = Department.const_get(constant_name)
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
data/test/test_helper.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
18
|
+
date: 2010-06-26 00:00:00 -07:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|