acts_as_lookup 0.0.3 → 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.rdoc +6 -0
- data/TODO +8 -13
- data/acts_as_lookup.gemspec +9 -5
- data/lib/acts_as_lookup.rb +45 -42
- data/spec/acts_as_lookup_spec.rb +436 -0
- data/spec/spec_helper.rb +1 -0
- metadata +23 -8
data/README.rdoc
CHANGED
@@ -45,3 +45,9 @@ Some work is yet to be done to support general classes using acts_as_lookup
|
|
45
45
|
== Configuration acts_as_lookup
|
46
46
|
|
47
47
|
TODO: go through the options (enabled and FUTURE)
|
48
|
+
|
49
|
+
== Development
|
50
|
+
|
51
|
+
=== Testing
|
52
|
+
|
53
|
+
Run rake spec to run the (rspec) tests. You should add tests for anything you touch.
|
data/TODO
CHANGED
@@ -1,22 +1,17 @@
|
|
1
1
|
Things to work on:
|
2
2
|
|
3
3
|
|
4
|
-
* find a better workaround for the cached instance problem (see https://rails.lighthouseapp.com/projects/8994/tickets/1339, for example) than explicit requiring of lookup classes (see acts_as_lookup.rb : ActsAsLookupHasLookupClassMethods#has_lookup)
|
5
4
|
* for now, it's required to include :id in the values hashes; enhance to allow leaving :id off (by specifying :uniqueness_column) and fetch them from db (option will only be available for :sync_with_db == true)
|
6
5
|
* allow gem to be used outside Rails by specifying alternative implementations for db-hitting methods....e.g. add configuration values that specify method names to call to load/write/remove from db. see +acts_as_lookup_fetch_values+ method comment.
|
7
6
|
* add the following configuration options:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
**
|
15
|
-
|
16
|
-
** remove_from_db: if there is an id in the table not specified in the model, will remove that row (removal will happen before any inserts)
|
17
|
-
*** allows for there to be values in lookup table that aren't relevant for the rails app (e.g. something that only some other process needs)
|
18
|
-
*** default will be false
|
19
|
-
** indexed_columns: which columns can be used in lookup_by_column methods
|
7
|
+
* if :sync_with_db false, don't have the requirement that there's an :id column specified
|
8
|
+
** so maybe this no-table business will be FUTURE only...
|
9
|
+
** default will be true
|
10
|
+
* write_to_db: if true will insert values specified in model into db table if the row doesn't exist (based on id)
|
11
|
+
** default will be true
|
12
|
+
* remove_from_db: if there is an id in the table not specified in the model, will remove that row (removal will happen before any inserts)
|
13
|
+
** allows for there to be values in lookup table that aren't relevant for the rails app (e.g. something that only some other process needs)
|
14
|
+
** default will be false
|
20
15
|
*** allows for specifying other values in the model that don't need to be lookupable columns, e.g. if there are columns with non-unique values, foreign keys to other tables etc.
|
21
16
|
*** default will be to use all columns specified in the model
|
22
17
|
*** probably store the lookup caches in one giant hash, each entry is a hash :column => lookup_hash_for_that_column
|
data/acts_as_lookup.gemspec
CHANGED
@@ -5,18 +5,18 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{acts_as_lookup}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Brian Percival"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-02-23}
|
13
13
|
s.description = %q{Provides an easy means for creating models that act like enumerations or lookup
|
14
14
|
tables. You can specify the lookup values in your Rails models and can lazily
|
15
15
|
push these to the associated db tables (or not). Also dynamically adds helpful
|
16
16
|
class-level methods to access singleton instances of each value in your lookup
|
17
17
|
table.
|
18
18
|
}
|
19
|
-
s.email = %q{
|
19
|
+
s.email = %q{percivalatdiscovereadsdotcom}
|
20
20
|
s.extra_rdoc_files = [
|
21
21
|
"LICENSE",
|
22
22
|
"README.rdoc",
|
@@ -30,14 +30,18 @@ table.
|
|
30
30
|
s.homepage = %q{http://github.com/bmpercy/acts_as_lookup}
|
31
31
|
s.rdoc_options = ["--charset=UTF-8"]
|
32
32
|
s.require_paths = ["lib"]
|
33
|
-
s.rubygems_version = %q{1.3.
|
33
|
+
s.rubygems_version = %q{1.3.7}
|
34
34
|
s.summary = %q{Helpful for creating lookup-table-like models}
|
35
|
+
s.test_files = [
|
36
|
+
"spec/acts_as_lookup_spec.rb",
|
37
|
+
"spec/spec_helper.rb"
|
38
|
+
]
|
35
39
|
|
36
40
|
if s.respond_to? :specification_version then
|
37
41
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
38
42
|
s.specification_version = 3
|
39
43
|
|
40
|
-
if Gem::Version.new(Gem::
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
41
45
|
else
|
42
46
|
end
|
43
47
|
else
|
data/lib/acts_as_lookup.rb
CHANGED
@@ -2,34 +2,36 @@
|
|
2
2
|
# method
|
3
3
|
#-------------------------------------------------------------------------------
|
4
4
|
module ActsAsLookupClassMethods
|
5
|
-
def
|
6
|
-
|
5
|
+
def self.extended(klass)
|
6
|
+
klass.send(:instance_variable_set, :@acts_as_lookup_options, nil)
|
7
|
+
klass.send(:instance_variable_set, :@acts_as_lookup_by_id, {})
|
8
|
+
klass.send(:instance_variable_set, :@acts_as_lookup_by_name, {})
|
9
|
+
klass.send(:instance_variable_set, :@acts_as_lookup_initialized, false)
|
10
|
+
klass.send(:instance_variable_set, :@acts_as_lookup_values, [])
|
7
11
|
end
|
8
12
|
|
13
|
+
def acts_as_lookup_options=(options)
|
14
|
+
@acts_as_lookup_options = options
|
15
|
+
end
|
9
16
|
def acts_as_lookup_options
|
10
|
-
|
17
|
+
@acts_as_lookup_options
|
11
18
|
end
|
12
19
|
|
13
|
-
|
14
20
|
# FUTURE: allow for dynamically specifying which columns can be used for
|
15
21
|
# cache lookups
|
16
22
|
#-----------------------------------------------------------------------------
|
17
23
|
def lookup_by_id(id)
|
18
|
-
|
24
|
+
@acts_as_lookup_by_id[id]
|
19
25
|
end
|
20
26
|
def lookup_by_name(name)
|
21
|
-
|
27
|
+
@acts_as_lookup_by_name[name]
|
22
28
|
end
|
23
29
|
|
24
30
|
|
25
31
|
# check if the current lookup class has been initialized for lookup use
|
26
32
|
#-----------------------------------------------------------------------------
|
27
33
|
def acts_as_lookup_initialized?
|
28
|
-
|
29
|
-
@@acts_as_lookup_initialized = false
|
30
|
-
end
|
31
|
-
|
32
|
-
@@acts_as_lookup_initialized
|
34
|
+
@acts_as_lookup_initialized
|
33
35
|
end
|
34
36
|
|
35
37
|
|
@@ -42,25 +44,22 @@ module ActsAsLookupClassMethods
|
|
42
44
|
Thread.exclusive do
|
43
45
|
# double-check in case of race condition in calling code
|
44
46
|
unless self.acts_as_lookup_initialized?
|
45
|
-
|
46
|
-
@@acts_as_lookup_by_name = {}
|
47
|
-
|
48
|
-
if @@acts_as_lookup_options[:sync_with_db]
|
47
|
+
if acts_as_lookup_options[:sync_with_db]
|
49
48
|
acts_as_lookup_fetch_values
|
50
|
-
if
|
49
|
+
if acts_as_lookup_options[:write_to_db]
|
51
50
|
acts_as_lookup_write_missing_values
|
52
51
|
end
|
53
52
|
else
|
54
|
-
|
53
|
+
@acts_as_lookup_values = acts_as_lookup_options[:values]
|
55
54
|
self.acts_as_lookup_refresh_caches
|
56
55
|
end
|
57
56
|
|
58
|
-
|
57
|
+
@acts_as_lookup_initialized = true
|
59
58
|
end
|
60
59
|
|
61
60
|
# FUTURE: allow for a different column to be used for generating class
|
62
61
|
# accessor methods
|
63
|
-
|
62
|
+
@acts_as_lookup_by_name.each_pair do |name,val|
|
64
63
|
self.acts_as_lookup_add_shortcut name
|
65
64
|
end
|
66
65
|
|
@@ -69,6 +68,7 @@ module ActsAsLookupClassMethods
|
|
69
68
|
|
70
69
|
|
71
70
|
# fetches existing records from the db and merges them into the cached values
|
71
|
+
# only called if :sync_with_db option is true
|
72
72
|
#
|
73
73
|
# FUTURE: if this gem is to be used outside of a Rails' ActiveRecord::Base
|
74
74
|
# descendant, will need to allow calling class to specify an alternative
|
@@ -76,23 +76,23 @@ module ActsAsLookupClassMethods
|
|
76
76
|
# call)
|
77
77
|
#-----------------------------------------------------------------------------
|
78
78
|
def acts_as_lookup_fetch_values
|
79
|
-
|
79
|
+
@acts_as_lookup_values = self.all
|
80
80
|
self.acts_as_lookup_refresh_caches
|
81
81
|
end
|
82
82
|
|
83
83
|
|
84
84
|
# writes any missing values to the db
|
85
|
+
# only called if :sync_with_db and :write_to_db options are both true
|
85
86
|
#
|
86
87
|
# NOTE: does NOT overwrite any values found in the db, so it is possible for
|
87
88
|
# the values specified in the class to be superceded by values in the
|
88
89
|
# database
|
89
90
|
#-----------------------------------------------------------------------------
|
90
91
|
def acts_as_lookup_write_missing_values
|
91
|
-
|
92
92
|
# FUTURE: if :ids aren't provided, use the uniqueness_column to determine
|
93
93
|
# which values are missing from existing caches
|
94
|
-
|
95
|
-
next if
|
94
|
+
acts_as_lookup_options[:values].each do |val|
|
95
|
+
next if @acts_as_lookup_by_id.include?(val[:id])
|
96
96
|
|
97
97
|
# allow for attr_accessible protection, assign attributes one-by-one
|
98
98
|
new_val = self.new
|
@@ -101,7 +101,7 @@ module ActsAsLookupClassMethods
|
|
101
101
|
end
|
102
102
|
new_val.save!
|
103
103
|
|
104
|
-
|
104
|
+
@acts_as_lookup_values << new_val
|
105
105
|
end
|
106
106
|
|
107
107
|
self.acts_as_lookup_refresh_caches
|
@@ -113,9 +113,9 @@ module ActsAsLookupClassMethods
|
|
113
113
|
def acts_as_lookup_refresh_caches
|
114
114
|
# FUTURE: this will get cleaned up, and will dynamically select which
|
115
115
|
# columns to establish lookup caches for.
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
@acts_as_lookup_values.each do |val|
|
117
|
+
@acts_as_lookup_by_id.merge!(val.id => val) unless @acts_as_lookup_by_id.include?(val.id)
|
118
|
+
@acts_as_lookup_by_name.merge!(val.name => val) unless @acts_as_lookup_by_name.include?(val.name)
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -134,7 +134,6 @@ module ActsAsLookupClassMethods
|
|
134
134
|
# to lookup by name
|
135
135
|
#-----------------------------------------------------------------------------
|
136
136
|
def acts_as_lookup_add_shortcut(name)
|
137
|
-
|
138
137
|
method_name = name.gsub(/ /, '_')
|
139
138
|
unless method_name.upcase == method_name
|
140
139
|
method_name.downcase!
|
@@ -146,7 +145,6 @@ module ActsAsLookupClassMethods
|
|
146
145
|
|
147
146
|
instance_eval "def #{method_name}; self.lookup_by_name '#{name}'; end"
|
148
147
|
end
|
149
|
-
|
150
148
|
end
|
151
149
|
|
152
150
|
# modify object to allow any class to act like a lookup class
|
@@ -161,8 +159,8 @@ class Object
|
|
161
159
|
def self.acts_as_lookup(options = {})
|
162
160
|
self.extend ActsAsLookupClassMethods
|
163
161
|
|
164
|
-
options.
|
165
|
-
|
162
|
+
options.merge!(:sync_with_db => true) unless options.include?(:sync_with_db)
|
163
|
+
options.merge!(:write_to_db => true) unless options.include?(:write_to_db)
|
166
164
|
# FUTURE:
|
167
165
|
# :remove_from_db => false,
|
168
166
|
# :shortcut_method_column => :name
|
@@ -189,21 +187,14 @@ if defined?(ActiveRecord)
|
|
189
187
|
# classname (in CamelCase) for the association.
|
190
188
|
#---------------------------------------------------------------------------
|
191
189
|
def has_lookup(association_name, options = {})
|
190
|
+
cname = options[:class_name] || association_name.to_s.camelize
|
192
191
|
|
193
|
-
|
194
|
-
|
195
|
-
# this is a hack that is not at all pretty but seems to get around the
|
196
|
-
# double-class loading problems that arise in rails: see for example
|
197
|
-
# https://rails.lighthouseapp.com/projects/8994/tickets/1339
|
198
|
-
# it may create other problems though, so be careful....
|
199
|
-
if !Object.const_defined?(class_name)
|
200
|
-
require File.join(RAILS_ROOT, 'app', 'models', class_name.underscore)
|
201
|
-
end
|
192
|
+
force_class_load cname
|
202
193
|
|
203
194
|
# this is inspired/borrowed from Rapleaf's has_rap_enum
|
204
|
-
klass = Kernel.const_get(
|
195
|
+
klass = Kernel.const_get(cname)
|
205
196
|
unless(klass && klass.is_a?(ActsAsLookupClassMethods))
|
206
|
-
raise "#{
|
197
|
+
raise "#{cname.to_s.camelize} is not an acts_as_lookup class"
|
207
198
|
end
|
208
199
|
|
209
200
|
# create the reader method for the lookup association
|
@@ -220,6 +211,18 @@ if defined?(ActiveRecord)
|
|
220
211
|
end
|
221
212
|
end
|
222
213
|
|
214
|
+
# separate the logic for forcing a class load to isolate it, as well as
|
215
|
+
# making testing easier
|
216
|
+
#---------------------------------------------------------------------------
|
217
|
+
def force_class_load(cname)
|
218
|
+
# this is a hack that is not at all pretty but seems to get around the
|
219
|
+
# double-class loading problems that arise in rails: see for example
|
220
|
+
# https://rails.lighthouseapp.com/projects/8994/tickets/1339
|
221
|
+
# it may create other problems though, so be careful....
|
222
|
+
if !Object.const_defined?(cname)
|
223
|
+
require File.join(RAILS_ROOT, 'app', 'models', cname.underscore)
|
224
|
+
end
|
225
|
+
end
|
223
226
|
end
|
224
227
|
|
225
228
|
ActiveRecord::Base.extend ActsAsLookupHasLookupClassMethods
|
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Base
|
5
|
+
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# add a helper method that's defined in rails...the gem only uses it if
|
10
|
+
# ActiveRecord is defined
|
11
|
+
class String
|
12
|
+
def camelize
|
13
|
+
splits = self.split("_")
|
14
|
+
splits.map! do |s|
|
15
|
+
s.downcase!
|
16
|
+
s[0] = s[0].upcase
|
17
|
+
s
|
18
|
+
end
|
19
|
+
splits.join
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# a dummy lookup class (see has_lookup tests)
|
24
|
+
class DummyLookup < Struct.new(:id, :name)
|
25
|
+
def self.one_instance
|
26
|
+
self.new(1,"dummy one")
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.another_instance
|
30
|
+
self.new(2,"dummy two")
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.yet_another_instance
|
34
|
+
self.new(3,"dummy three")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "ActsAsLookup" do
|
39
|
+
# this is kinda hacky, but what we're doing here is trying to clear out all
|
40
|
+
# entries in namespace for acts as lookup between each run so that we don't
|
41
|
+
# see any bugs that are tied to double-loading the acts as lookup
|
42
|
+
# infrastructure
|
43
|
+
# so we:
|
44
|
+
# - unload all classes that are either part of gem or are test classes
|
45
|
+
# - reload the acts as lookup lib file
|
46
|
+
# - re-declare each of the test classes we're using
|
47
|
+
#
|
48
|
+
# (alternative would be to generate new classes for each test but that's
|
49
|
+
# likely equally messy with dynamically defined class names etc.)
|
50
|
+
#-----------------------------------------------------------------------------
|
51
|
+
before :each do
|
52
|
+
Object.constants.each do |klass|
|
53
|
+
if (klass.to_s =~ /LookupClass/) && (Object.const_defined?(klass.to_s))
|
54
|
+
Object.send(:remove_const,klass.to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
load File.join(File.dirname(__FILE__), '../lib/acts_as_lookup.rb')
|
58
|
+
|
59
|
+
class ClassOnlyLookupClass < Struct.new(:id, :name); end
|
60
|
+
|
61
|
+
# another one to detect shared var conflict
|
62
|
+
class SecondClassOnlyLookupClass < Struct.new(:id, :name); end
|
63
|
+
|
64
|
+
# all values are in db and in class specification
|
65
|
+
class ActiveRecordLookupClassWithAllValuesInDbAndClass < Struct.new(:id, :name)
|
66
|
+
ALL_VALUES = [self.new(1, "one"), self.new(2, "two")]
|
67
|
+
|
68
|
+
def self.all_values
|
69
|
+
ALL_VALUES
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.class_vals
|
73
|
+
all_values
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.all
|
77
|
+
all_values
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# some values are in db; the rest are in class specification
|
81
|
+
class ActiveRecordLookupClassWithSomeValuesInDb < Struct.new(:id, :name)
|
82
|
+
ALL_VALUES = [self.new(1, "one"), self.new(2, "two"), self.new(3, "three")]
|
83
|
+
|
84
|
+
def self.all_values
|
85
|
+
ALL_VALUES
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.db_missing_val
|
89
|
+
all_values[1]
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.class_vals
|
93
|
+
all_values
|
94
|
+
end
|
95
|
+
|
96
|
+
# don't return all vals in query
|
97
|
+
def self.all
|
98
|
+
all_values.reject { |v| v.id == db_missing_val.id }
|
99
|
+
end
|
100
|
+
|
101
|
+
# a method for checking what's saved when save! is called
|
102
|
+
def self.created_val(val)
|
103
|
+
# doesn't need to do anything
|
104
|
+
end
|
105
|
+
|
106
|
+
# "active record" method for creating new lookup values
|
107
|
+
def save!
|
108
|
+
self.class.created_val(self)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
# all values in db; some are in class specification
|
112
|
+
class ActiveRecordLookupClassWithSomeValuesInClass < Struct.new(:id, :name)
|
113
|
+
ALL_VALUES = [self.new(1, "one"), self.new(2, "two"), self.new(3, "three")]
|
114
|
+
|
115
|
+
def self.all_values
|
116
|
+
ALL_VALUES
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.class_missing_val
|
120
|
+
all_values[1]
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.class_vals
|
124
|
+
all_values.reject { |v| v.id == class_missing_val.id }
|
125
|
+
end
|
126
|
+
|
127
|
+
# return all vals in query
|
128
|
+
def self.all
|
129
|
+
all_values
|
130
|
+
end
|
131
|
+
end
|
132
|
+
class ClassWithLookupClass < ActiveRecord::Base
|
133
|
+
attr_accessor :dummy_lookup_id
|
134
|
+
attr_accessor :other_lookup_id
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
#-----------------------------------------------------------------------------
|
140
|
+
describe "class-only (non ActiveRecord) lookup classes" do
|
141
|
+
it "should not insert any new values if :write_to_db is false" do
|
142
|
+
klass = ClassOnlyLookupClass
|
143
|
+
klass.should_not_receive :acts_as_lookup_write_missing_values
|
144
|
+
instances = [klass.new(1, "one"), klass.new(2, "two")]
|
145
|
+
|
146
|
+
klass.acts_as_lookup(
|
147
|
+
:sync_with_db => false,
|
148
|
+
:values => instances
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not query values if :sync_with_db is false" do
|
153
|
+
klass = ClassOnlyLookupClass
|
154
|
+
klass.should_not_receive :all
|
155
|
+
instances = [klass.new(1, "one"), klass.new(2, "two")]
|
156
|
+
|
157
|
+
klass.acts_as_lookup(
|
158
|
+
:sync_with_db => false,
|
159
|
+
:values => instances
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# "active record" tests
|
165
|
+
# note: these test classes aren't really active record classes (to enable
|
166
|
+
# testing in absence of active record), but mimic enough of the
|
167
|
+
# interface for the tests
|
168
|
+
|
169
|
+
#-----------------------------------------------------------------------------
|
170
|
+
describe "when active record lookup class specifies all values and db has all values" do
|
171
|
+
it "should run select query if :sync_with_db is true" do
|
172
|
+
klass = ActiveRecordLookupClassWithAllValuesInDbAndClass
|
173
|
+
|
174
|
+
klass.should_receive(:all).and_return klass.all_values
|
175
|
+
|
176
|
+
klass.acts_as_lookup(
|
177
|
+
:values => klass.class_vals
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should not insert any new values even if :write_to_db is true" do
|
182
|
+
klass = ActiveRecordLookupClassWithAllValuesInDbAndClass
|
183
|
+
|
184
|
+
klass.should_not_receive(:new)
|
185
|
+
|
186
|
+
klass.acts_as_lookup(
|
187
|
+
:write_to_db => true,
|
188
|
+
:values => klass.class_vals
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should return correct value accessed by lookup_by_id" do
|
193
|
+
klass = ActiveRecordLookupClassWithAllValuesInDbAndClass
|
194
|
+
klass.acts_as_lookup(
|
195
|
+
:write_to_db => true,
|
196
|
+
:values => klass.class_vals
|
197
|
+
)
|
198
|
+
|
199
|
+
klass.lookup_by_id(klass.all_values.first.id).should == klass.all_values.first
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should return correct value accessed by lookup_by_name" do
|
203
|
+
klass = ActiveRecordLookupClassWithAllValuesInDbAndClass
|
204
|
+
klass.acts_as_lookup(
|
205
|
+
:write_to_db => true,
|
206
|
+
:values => klass.class_vals
|
207
|
+
)
|
208
|
+
|
209
|
+
klass.lookup_by_name(klass.all_values.last.name).should == klass.all_values.last
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
#-----------------------------------------------------------------------------
|
214
|
+
describe "when lookup class specifies all lookup values but db only specifies some" do
|
215
|
+
before :each do
|
216
|
+
@klass = ActiveRecordLookupClassWithSomeValuesInDb
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should run select query if :sync_with_db is true" do
|
220
|
+
all_result = @klass.all
|
221
|
+
|
222
|
+
@klass.should_receive(:all).and_return all_result
|
223
|
+
|
224
|
+
@klass.acts_as_lookup(
|
225
|
+
:values => @klass.class_vals
|
226
|
+
)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should insert any new values when :write_to_db is true" do
|
230
|
+
# this is a dummy class method that gets called when instance method
|
231
|
+
# save! is called, so we can check what was created
|
232
|
+
@klass.should_receive(:created_val) { |val|
|
233
|
+
val.id.should == @klass.db_missing_val.id
|
234
|
+
val.name.should == @klass.db_missing_val.name
|
235
|
+
}
|
236
|
+
|
237
|
+
@klass.acts_as_lookup(
|
238
|
+
:write_to_db => true,
|
239
|
+
:values => @klass.class_vals
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should return correct value accessed by lookup_by_id" do
|
244
|
+
@klass.acts_as_lookup(
|
245
|
+
:write_to_db => true,
|
246
|
+
:values => @klass.class_vals
|
247
|
+
)
|
248
|
+
|
249
|
+
@klass.lookup_by_id(@klass.db_missing_val.id).should == @klass.db_missing_val
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should return correct value accessed by lookup_by_name" do
|
253
|
+
@klass.acts_as_lookup(
|
254
|
+
:write_to_db => true,
|
255
|
+
:values => @klass.class_vals
|
256
|
+
)
|
257
|
+
|
258
|
+
@klass.lookup_by_name(@klass.db_missing_val.name).should == @klass.db_missing_val
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
#-----------------------------------------------------------------------------
|
263
|
+
describe "when lookup class doesn't specify all lookup values but db does" do
|
264
|
+
before :each do
|
265
|
+
@klass = ActiveRecordLookupClassWithSomeValuesInClass
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should run select query if :sync_with_db is true" do
|
269
|
+
all_result = @klass.all
|
270
|
+
|
271
|
+
@klass.should_receive(:all).and_return all_result
|
272
|
+
|
273
|
+
@klass.acts_as_lookup(
|
274
|
+
:values => @klass.class_vals
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
it "shouldn't insert any new values even if :write_to_db is true" do
|
279
|
+
@klass.should_not_receive(:new)
|
280
|
+
|
281
|
+
@klass.acts_as_lookup(
|
282
|
+
:write_to_db => true,
|
283
|
+
:values => @klass.class_vals
|
284
|
+
)
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should return correct value accessed by lookup_by_id" do
|
288
|
+
@klass.acts_as_lookup(
|
289
|
+
:write_to_db => true,
|
290
|
+
:values => @klass.class_vals
|
291
|
+
)
|
292
|
+
|
293
|
+
@klass.lookup_by_id(@klass.class_missing_val.id).should == @klass.class_missing_val
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should return correct value accessed by lookup_by_name" do
|
297
|
+
@klass.acts_as_lookup(
|
298
|
+
:write_to_db => true,
|
299
|
+
:values => @klass.class_vals
|
300
|
+
)
|
301
|
+
|
302
|
+
@klass.lookup_by_name(@klass.class_missing_val.name).should == @klass.class_missing_val
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
#-----------------------------------------------------------------------------
|
307
|
+
describe "single-lookup-class scenarios" do
|
308
|
+
before :each do
|
309
|
+
@klass = ActiveRecordLookupClassWithSomeValuesInClass
|
310
|
+
@klass.acts_as_lookup(:values => @klass.class_vals)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should not query values on calls to lookup_by_id" do
|
314
|
+
@klass.should_not_receive(:find)
|
315
|
+
@klass.should_not_receive(:find_by_id)
|
316
|
+
|
317
|
+
@klass.lookup_by_id(1)
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should not query values calls to lookup_by_name" do
|
321
|
+
@klass.should_not_receive(:find)
|
322
|
+
@klass.should_not_receive(:find_by_name)
|
323
|
+
|
324
|
+
@klass.lookup_by_name('one')
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should dynamically add specific methods to access lookup value by name" do
|
328
|
+
@klass.all_values.each do |val|
|
329
|
+
@klass.send(val.name.to_sym).should == val
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe "two-lookup-class scenarios" do
|
335
|
+
it "should return the correct object (type) even if ids overlap between two lookup classes" do
|
336
|
+
klass1 = ClassOnlyLookupClass
|
337
|
+
klass2 = SecondClassOnlyLookupClass
|
338
|
+
instances1 = [klass1.new(1, "one"), klass1.new(2, "two")]
|
339
|
+
instances2 = [klass2.new(1, "class two one"), klass2.new(2, "class two two")]
|
340
|
+
klass1.acts_as_lookup(
|
341
|
+
:sync_with_db => false,
|
342
|
+
:values => instances1
|
343
|
+
)
|
344
|
+
klass2.acts_as_lookup(
|
345
|
+
:sync_with_db => false,
|
346
|
+
:values => instances2
|
347
|
+
)
|
348
|
+
|
349
|
+
klass1.lookup_by_id(1).should == instances1.first
|
350
|
+
klass2.lookup_by_id(1).should == instances2.first
|
351
|
+
end
|
352
|
+
|
353
|
+
it "should return the correct object (type) even if names overlap between two lookup classes" do
|
354
|
+
klass1 = ClassOnlyLookupClass
|
355
|
+
klass2 = SecondClassOnlyLookupClass
|
356
|
+
instances1 = [klass1.new(3, "three"), klass1.new(4, "four")]
|
357
|
+
instances2 = [klass2.new(33, "three"), klass2.new(44, "four")]
|
358
|
+
klass1.acts_as_lookup(
|
359
|
+
:sync_with_db => false,
|
360
|
+
:values => instances1
|
361
|
+
)
|
362
|
+
klass2.acts_as_lookup(
|
363
|
+
:sync_with_db => false,
|
364
|
+
:values => instances2
|
365
|
+
)
|
366
|
+
|
367
|
+
klass1.lookup_by_name('four').should == instances1.last
|
368
|
+
klass2.lookup_by_name('four').should == instances2.last
|
369
|
+
end
|
370
|
+
|
371
|
+
it "should keep acts as lookup options separate for different lookup classes" do
|
372
|
+
klass1 = ClassOnlyLookupClass
|
373
|
+
klass2 = SecondClassOnlyLookupClass
|
374
|
+
instances1 = [klass1.new(1, "one"), klass1.new(2, "two")]
|
375
|
+
instances2 = [klass2.new(3, "three"), klass2.new(4, "four")]
|
376
|
+
klass1.acts_as_lookup(
|
377
|
+
:sync_with_db => false,
|
378
|
+
:values => instances1
|
379
|
+
)
|
380
|
+
klass2.acts_as_lookup(
|
381
|
+
:sync_with_db => false,
|
382
|
+
:values => instances2
|
383
|
+
)
|
384
|
+
|
385
|
+
klass1.acts_as_lookup_options[:values].should == instances1
|
386
|
+
klass2.acts_as_lookup_options[:values].should == instances2
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
describe "when using has_lookup to associate classes" do
|
391
|
+
it "should not make a db hit to look up a single record when accessing a has_lookup accessor" do
|
392
|
+
DummyLookup.acts_as_lookup(
|
393
|
+
:values => [DummyLookup.one_instance],
|
394
|
+
:sync_with_db => false
|
395
|
+
)
|
396
|
+
klass = ClassWithLookupClass
|
397
|
+
klass.has_lookup :dummy_lookup
|
398
|
+
lookup_instance = DummyLookup.one_instance
|
399
|
+
|
400
|
+
instance = klass.new
|
401
|
+
instance.dummy_lookup_id = lookup_instance.id
|
402
|
+
|
403
|
+
instance.dummy_lookup.should == lookup_instance
|
404
|
+
end
|
405
|
+
|
406
|
+
it "should set the id attribute of an object that has_lookup when the whole-object setter is used" do
|
407
|
+
DummyLookup.acts_as_lookup(
|
408
|
+
:values => [DummyLookup.another_instance],
|
409
|
+
:sync_with_db => false
|
410
|
+
)
|
411
|
+
klass = ClassWithLookupClass
|
412
|
+
klass.has_lookup :dummy_lookup
|
413
|
+
lookup_instance = DummyLookup.one_instance
|
414
|
+
|
415
|
+
instance = klass.new
|
416
|
+
instance.dummy_lookup = lookup_instance
|
417
|
+
|
418
|
+
instance.dummy_lookup_id.should == lookup_instance.id
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should allow overriding the class name on a has_lookup association" do
|
422
|
+
DummyLookup.acts_as_lookup(
|
423
|
+
:values => [DummyLookup.yet_another_instance],
|
424
|
+
:sync_with_db => false
|
425
|
+
)
|
426
|
+
klass = ClassWithLookupClass
|
427
|
+
klass.has_lookup :other_lookup, :class_name => 'DummyLookup'
|
428
|
+
lookup_instance = DummyLookup.one_instance
|
429
|
+
|
430
|
+
instance = klass.new
|
431
|
+
instance.other_lookup = lookup_instance
|
432
|
+
|
433
|
+
instance.other_lookup_id.should == lookup_instance.id
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/acts_as_lookup')
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_lookup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Brian Percival
|
@@ -9,7 +15,7 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-02-23 00:00:00 -08:00
|
13
19
|
default_executable:
|
14
20
|
dependencies: []
|
15
21
|
|
@@ -20,7 +26,7 @@ description: |
|
|
20
26
|
class-level methods to access singleton instances of each value in your lookup
|
21
27
|
table.
|
22
28
|
|
23
|
-
email:
|
29
|
+
email: percivalatdiscovereadsdotcom
|
24
30
|
executables: []
|
25
31
|
|
26
32
|
extensions: []
|
@@ -35,6 +41,8 @@ files:
|
|
35
41
|
- lib/acts_as_lookup.rb
|
36
42
|
- LICENSE
|
37
43
|
- TODO
|
44
|
+
- spec/acts_as_lookup_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
38
46
|
has_rdoc: true
|
39
47
|
homepage: http://github.com/bmpercy/acts_as_lookup
|
40
48
|
licenses: []
|
@@ -45,23 +53,30 @@ rdoc_options:
|
|
45
53
|
require_paths:
|
46
54
|
- lib
|
47
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
48
57
|
requirements:
|
49
58
|
- - ">="
|
50
59
|
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
51
63
|
version: "0"
|
52
|
-
version:
|
53
64
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
54
66
|
requirements:
|
55
67
|
- - ">="
|
56
68
|
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
57
72
|
version: "0"
|
58
|
-
version:
|
59
73
|
requirements: []
|
60
74
|
|
61
75
|
rubyforge_project:
|
62
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.7
|
63
77
|
signing_key:
|
64
78
|
specification_version: 3
|
65
79
|
summary: Helpful for creating lookup-table-like models
|
66
|
-
test_files:
|
67
|
-
|
80
|
+
test_files:
|
81
|
+
- spec/acts_as_lookup_spec.rb
|
82
|
+
- spec/spec_helper.rb
|