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 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
+