acts_as_lookup 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +47 -0
- data/TODO +30 -0
- data/acts_as_lookup.gemspec +46 -0
- data/lib/acts_as_lookup.rb +208 -0
- metadata +67 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright (c) 2010 Umamibud, Inc.
|
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,47 @@
|
|
1
|
+
= acts_as_lookup
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
Provides an easy means for creating models that act like enumerations or lookup tables. You can specify the lookup values in your Rails models and can lazily push these to the associated db tables (or not). Also dynamically adds helpful class-level methods to access singleton instances of each value in your lookup
|
6
|
+
|
7
|
+
== What to do
|
8
|
+
|
9
|
+
=== Rails models
|
10
|
+
|
11
|
+
==== Defining a lookup model
|
12
|
+
|
13
|
+
In your model, do something like:
|
14
|
+
|
15
|
+
def MyLookup < ActiveRecord::Base
|
16
|
+
VALS = [ { :id => 1, :name => "value 1", ... other attributes },
|
17
|
+
{ :id => 2, :name => "value 2", ... other attributes },
|
18
|
+
... other lookup values ...
|
19
|
+
]
|
20
|
+
acts_as_lookup :values => VALS, ... other options ...
|
21
|
+
end
|
22
|
+
|
23
|
+
==== Associating a model with a lookup model
|
24
|
+
|
25
|
+
In your model, you can specify an association to a lookup using +has_lookup+:
|
26
|
+
|
27
|
+
def MyModel < ActiveRecord::Base
|
28
|
+
has_lookup :my_lookup
|
29
|
+
end
|
30
|
+
|
31
|
+
Alternatively, you can explicitly state the class name of the associated lookup:
|
32
|
+
|
33
|
+
def MyModel < ActiveRecord::Base
|
34
|
+
has_lookup :my_association, :class_name => 'MyLookup'
|
35
|
+
end
|
36
|
+
|
37
|
+
==== Alternative: table-less lookups and associations
|
38
|
+
|
39
|
+
TODO: probably won't work yet...at least need to add an _id attribute to the associating class....
|
40
|
+
|
41
|
+
=== Other (FUTURE)
|
42
|
+
|
43
|
+
Some work is yet to be done to support general classes using acts_as_lookup
|
44
|
+
|
45
|
+
== Configuration acts_as_lookup
|
46
|
+
|
47
|
+
TODO: go through the options (enabled and FUTURE)
|
data/TODO
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Things to work on:
|
2
|
+
|
3
|
+
* 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)
|
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)
|
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.
|
6
|
+
* add the following configuration options:
|
7
|
+
** sync_with_db: if false, no inserts/retrievals of db will be performed at all. this means the model will operate as an enum-like thing...a lot like rapleaf_enum where the class just defines the mapping and when associations are formed, the id gets inserted.
|
8
|
+
*** plan for this to work even if there is no lookup table in db at all...eg if we didn't care about foreign keys and just wanted to store some ids instead of strings....just need to make sure that associations work properly...like user.status = UserAccountStatus.active needs to set user.status_id = UserAccountStatus.active
|
9
|
+
*** this might be kind of tricky to do without mucking with the active record association methods (belongs_to() etc.)
|
10
|
+
*** if false, don't have the requirement that there's an :id column specified
|
11
|
+
*** so maybe this no-table business will be FUTURE only...
|
12
|
+
*** default will be true
|
13
|
+
** write_to_db: if true will insert values specified in model into db table if the row doesn't exist (based on id)
|
14
|
+
*** default will be true
|
15
|
+
** 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)
|
16
|
+
*** 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)
|
17
|
+
*** default will be false
|
18
|
+
** indexed_columns: which columns can be used in lookup_by_column methods
|
19
|
+
*** 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.
|
20
|
+
*** default will be to use all columns specified in the model
|
21
|
+
** uniqueness_column: which column defines uniqueness of a record if :id is not set in model
|
22
|
+
*** allows to leave off the id when specifying the rows in model if it doesn't really matter, but still do the inserting/removing of rows automatically
|
23
|
+
*** default will be :name (if :name not specified in the data in the model, then if uniqueness_column isn't set, there will be errors)
|
24
|
+
** shortcut_method_column: which column to use to create shortcut lookup methods by value
|
25
|
+
*** this is used to do the UserAccountStatus.active thing, allowing for doing it with a different column name other than :name if desired
|
26
|
+
*** default will be :name
|
27
|
+
*** see acts_as_lookup_add_shortcut method
|
28
|
+
** Config this in initializer or env.rb file (might want to be env-specific), hopefully we're fine with it being global to all lookup classes
|
29
|
+
*** initialize_on_class_load: force values to load and methods to be created when the class loads, instead of being done lazily
|
30
|
+
**** default will be false
|
@@ -0,0 +1,46 @@
|
|
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{acts_as_lookup}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Brian Percival"]
|
12
|
+
s.date = %q{2010-04-05}
|
13
|
+
s.description = %q{Provides an easy means for creating models that act like enumerations or lookup
|
14
|
+
tables. You can specify the lookup values in your Rails models and can lazily
|
15
|
+
push these to the associated db tables (or not). Also dynamically adds helpful
|
16
|
+
class-level methods to access singleton instances of each value in your lookup
|
17
|
+
table.
|
18
|
+
}
|
19
|
+
s.email = %q{percivalatumamibuddotcom}
|
20
|
+
s.extra_rdoc_files = [
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"TODO"
|
24
|
+
]
|
25
|
+
s.files = [
|
26
|
+
"README.rdoc",
|
27
|
+
"acts_as_lookup.gemspec",
|
28
|
+
"lib/acts_as_lookup.rb"
|
29
|
+
]
|
30
|
+
s.homepage = %q{http://github.com/bmpercy/acts_as_lookup}
|
31
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
32
|
+
s.require_paths = ["lib"]
|
33
|
+
s.rubygems_version = %q{1.3.5}
|
34
|
+
s.summary = %q{Helpful for creating lookup-table-like models}
|
35
|
+
|
36
|
+
if s.respond_to? :specification_version then
|
37
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
38
|
+
s.specification_version = 3
|
39
|
+
|
40
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
41
|
+
else
|
42
|
+
end
|
43
|
+
else
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# contains methods and logic to be added to classes that call the acts_as_lookup
|
2
|
+
# method
|
3
|
+
#-------------------------------------------------------------------------------
|
4
|
+
module ActsAsLookupClassMethods
|
5
|
+
def acts_as_lookup_options=(options)
|
6
|
+
@@acts_as_lookup_options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def acts_as_lookup_options
|
10
|
+
@@acts_as_lookup_options
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# FUTURE: allow for dynamically specifying which columns can be used for
|
15
|
+
# cache lookups
|
16
|
+
#-----------------------------------------------------------------------------
|
17
|
+
def lookup_by_id(id)
|
18
|
+
@@acts_as_lookup_by_id[id]
|
19
|
+
end
|
20
|
+
def lookup_by_name(name)
|
21
|
+
@@acts_as_lookup_by_name[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# check if the current lookup class has been initialized for lookup use
|
26
|
+
#-----------------------------------------------------------------------------
|
27
|
+
def acts_as_lookup_initialized?
|
28
|
+
if !defined?(@@acts_as_lookup_initialized)
|
29
|
+
@@acts_as_lookup_initialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
@@acts_as_lookup_initialized
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# initialize the current lookup class for lookup use if it isn't already
|
37
|
+
#
|
38
|
+
# NOTE: early edition of this gem assumes id, name columns. FUTURE will allow
|
39
|
+
# more flexibility through configuration
|
40
|
+
#-----------------------------------------------------------------------------
|
41
|
+
def acts_as_lookup_initialize
|
42
|
+
Thread.exclusive do
|
43
|
+
# double-check in case of race condition in calling code
|
44
|
+
unless self.acts_as_lookup_initialized?
|
45
|
+
@@acts_as_lookup_by_id = {}
|
46
|
+
@@acts_as_lookup_by_name = {}
|
47
|
+
|
48
|
+
if @@acts_as_lookup_options[:sync_with_db]
|
49
|
+
acts_as_lookup_fetch_values
|
50
|
+
if @@acts_as_lookup_options[:write_to_db]
|
51
|
+
acts_as_lookup_write_missing_values
|
52
|
+
end
|
53
|
+
else
|
54
|
+
@@acts_as_lookup_values = @acts_as_lookup_options[:values]
|
55
|
+
self.acts_as_lookup_refresh_caches
|
56
|
+
end
|
57
|
+
|
58
|
+
@@acts_as_lookup_initialized = true
|
59
|
+
end
|
60
|
+
|
61
|
+
# FUTURE: allow for a different column to be used for generating class
|
62
|
+
# accessor methods
|
63
|
+
@@acts_as_lookup_by_name.each_pair do |name,val|
|
64
|
+
self.acts_as_lookup_add_shortcut name
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# fetches existing records from the db and merges them into the cached values
|
72
|
+
#
|
73
|
+
# FUTURE: if this gem is to be used outside of a Rails' ActiveRecord::Base
|
74
|
+
# descendant, will need to allow calling class to specify an alternative
|
75
|
+
# implementation (maybe add a config value that specifies a method to
|
76
|
+
# call)
|
77
|
+
#-----------------------------------------------------------------------------
|
78
|
+
def acts_as_lookup_fetch_values
|
79
|
+
@@acts_as_lookup_values = self.all
|
80
|
+
self.acts_as_lookup_refresh_caches
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# writes any missing values to the db
|
85
|
+
#
|
86
|
+
# NOTE: does NOT overwrite any values found in the db, so it is possible for
|
87
|
+
# the values specified in the class to be superceded by values in the
|
88
|
+
# database
|
89
|
+
#-----------------------------------------------------------------------------
|
90
|
+
def acts_as_lookup_write_missing_values
|
91
|
+
|
92
|
+
# FUTURE: if :ids aren't provided, use the uniqueness_column to determine
|
93
|
+
# which values are missing from existing caches
|
94
|
+
@@acts_as_lookup_options[:values].each do |val|
|
95
|
+
next if @@acts_as_lookup_by_id.include?(val[:id])
|
96
|
+
|
97
|
+
# allow for attr_accessible protection, assign attributes one-by-one
|
98
|
+
new_val = self.new
|
99
|
+
val.each_pair do |attr,value|
|
100
|
+
new_val.send("#{attr.to_s}=".to_sym, value)
|
101
|
+
end
|
102
|
+
new_val.save!
|
103
|
+
|
104
|
+
@@acts_as_lookup_values << new_val
|
105
|
+
end
|
106
|
+
|
107
|
+
self.acts_as_lookup_refresh_caches
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# updates the lookup cache hashes from @@acts_as_lookup_values
|
112
|
+
#-----------------------------------------------------------------------------
|
113
|
+
def acts_as_lookup_refresh_caches
|
114
|
+
@@acts_as_lookup_values.each do |val|
|
115
|
+
@@acts_as_lookup_by_id.reverse_merge! val.id => val
|
116
|
+
end
|
117
|
+
@@acts_as_lookup_values.each do |val|
|
118
|
+
@@acts_as_lookup_by_name.reverse_merge! val.name => val
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# adds a class method to access a particular lookup value via a shortcut
|
124
|
+
# method
|
125
|
+
#
|
126
|
+
# FUTURE: will allow for any column to be used here; for now, hardcoded
|
127
|
+
# to lookup by name
|
128
|
+
#-----------------------------------------------------------------------------
|
129
|
+
def acts_as_lookup_add_shortcut(name)
|
130
|
+
instance_eval "def #{name}; self.lookup_by_name '#{name}'; end"
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# modify object to allow any class to act like a lookup class
|
136
|
+
#------------------------------------------------------------------------------
|
137
|
+
class Object
|
138
|
+
|
139
|
+
# converts the calling class to act like a lookup model.
|
140
|
+
#
|
141
|
+
# NOTE: for now, the values' name column should not have spaces in it,
|
142
|
+
# for cleanliness, though this can be addressed by gsubbing.
|
143
|
+
#-----------------------------------------------------------------------------
|
144
|
+
def self.acts_as_lookup(options = {})
|
145
|
+
self.extend ActsAsLookupClassMethods
|
146
|
+
|
147
|
+
options.reverse_merge! :sync_with_db => true,
|
148
|
+
:write_to_db => true #,
|
149
|
+
# FUTURE:
|
150
|
+
# :remove_from_db => false,
|
151
|
+
# :shortcut_method_column => :name
|
152
|
+
|
153
|
+
self.acts_as_lookup_options = options
|
154
|
+
|
155
|
+
# lazy initialize? but for now explicitly initialize here
|
156
|
+
self.acts_as_lookup_initialize
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
# class methods for ActiveRecord associations
|
162
|
+
if defined?(ActiveRecord)
|
163
|
+
module ActsAsLookupHasLookupClassMethods
|
164
|
+
|
165
|
+
# create an association from the current rails model to a lookup model,
|
166
|
+
# providing a name for the association (default will assume the
|
167
|
+
# association name follows rails association naming conventions).
|
168
|
+
#
|
169
|
+
# options:
|
170
|
+
# :class_name: override the default assumption of class name from
|
171
|
+
# +assocaition_name+ argument and explicitly pass in the
|
172
|
+
# classname (in CamelCase) for the association.
|
173
|
+
#---------------------------------------------------------------------------
|
174
|
+
def has_lookup(association_name, options = {})
|
175
|
+
|
176
|
+
class_name = options[:class_name] || association_name.to_s.camelize
|
177
|
+
|
178
|
+
# this is a hack that is not at all pretty but seems to get around the
|
179
|
+
# double-class loading problems that arise in rails: see for example
|
180
|
+
# https://rails.lighthouseapp.com/projects/8994/tickets/1339
|
181
|
+
# it may create other problems though, so be careful....
|
182
|
+
require File.join(RAILS_ROOT, 'app', 'models', class_name.underscore)
|
183
|
+
|
184
|
+
# this is inspired/borrowed from Rapleaf's has_rap_enum
|
185
|
+
klass = Kernel.const_get(class_name)
|
186
|
+
unless(klass && klass.is_a?(ActsAsLookupClassMethods))
|
187
|
+
raise "#{class_name.to_s.camelize} is not an acts_as_lookup class"
|
188
|
+
end
|
189
|
+
|
190
|
+
# create the reader method for the lookup association
|
191
|
+
define_method(association_name) do
|
192
|
+
klass.lookup_by_id(send("#{association_name}_id"))
|
193
|
+
end
|
194
|
+
|
195
|
+
# create the writer method for the lookup association
|
196
|
+
define_method("#{association_name}=") do |assoc|
|
197
|
+
unless (assoc.class.name == klass.name) || assoc.nil?
|
198
|
+
raise "Argument not of type #{klass.name}"
|
199
|
+
end
|
200
|
+
send("#{association_name}_id=", assoc && assoc.id)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
ActiveRecord::Base.extend ActsAsLookupHasLookupClassMethods
|
207
|
+
|
208
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_lookup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Percival
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-05 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: |
|
17
|
+
Provides an easy means for creating models that act like enumerations or lookup
|
18
|
+
tables. You can specify the lookup values in your Rails models and can lazily
|
19
|
+
push these to the associated db tables (or not). Also dynamically adds helpful
|
20
|
+
class-level methods to access singleton instances of each value in your lookup
|
21
|
+
table.
|
22
|
+
|
23
|
+
email: percivalatumamibuddotcom
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- LICENSE
|
30
|
+
- README.rdoc
|
31
|
+
- TODO
|
32
|
+
files:
|
33
|
+
- README.rdoc
|
34
|
+
- acts_as_lookup.gemspec
|
35
|
+
- lib/acts_as_lookup.rb
|
36
|
+
- LICENSE
|
37
|
+
- TODO
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/bmpercy/acts_as_lookup
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --charset=UTF-8
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.5
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Helpful for creating lookup-table-like models
|
66
|
+
test_files: []
|
67
|
+
|