hopsoft-acts-as-lookup 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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .idea
23
+ project_settings.xml
24
+ Project_Default.xml
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Nathan Hopkins
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 ADDED
@@ -0,0 +1,4 @@
1
+ ActsAsLookup
2
+ ============
3
+
4
+ Description goes here
@@ -0,0 +1,18 @@
1
+ = hopsoft_acts_as_lookup
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2009 Nathan Hopkins. See LICENSE for details.
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "hopsoft-acts-as-lookup"
8
+ gem.summary = "Lookup tables made easy for ActiveRecord"
9
+ gem.description = "Powerful lookup table behavior added to ActiveRecord."
10
+ gem.email = "natehop@gmail.com"
11
+ gem.homepage = "http://github.com/hopsoft/acts_as_lookup"
12
+ gem.authors = ["Nathan Hopkins"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "hopsoft_acts_as_lookup #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,15 @@
1
+ require 'rake'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.version = '0.1.0'
5
+ spec.name = 'hopsoft-acts-as-lookup'
6
+ spec.summary = 'Lookup tables made easy for ActiveRecord'
7
+ spec.description = <<-DESC
8
+ Powerful lookup table behavior added to ActiveRecord.
9
+ DESC
10
+ spec.authors = ['Nathan Hopkins']
11
+ spec.email = ['natehop@gmail.com']
12
+ spec.bindir = 'bin'
13
+ spec.files = FileList['lib/*.rb', 'lib/**/*.rb', 'bin/*'].to_a
14
+ end
15
+
@@ -0,0 +1,64 @@
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{hopsoft-acts-as-lookup}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nathan Hopkins"]
12
+ s.date = %q{2009-10-21}
13
+ s.description = %q{Powerful lookup table behavior added to ActiveRecord.}
14
+ s.email = %q{natehop@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "acts_as_lookup.gemspec",
29
+ "hopsoft-acts-as-lookup-0.1.0.gem",
30
+ "hopsoft-acts-as-lookup.gemspec",
31
+ "init.rb",
32
+ "install.rb",
33
+ "lib/hopsoft/acts_as_lookup.rb",
34
+ "tasks/acts_as_lookup_tasks.rake",
35
+ "test/acts_as_lookup_test.rb",
36
+ "test/helper.rb",
37
+ "test/test_hopsoft_acts_as_lookup.rb",
38
+ "uninstall.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/hopsoft/acts_as_lookup}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{Lookup tables made easy for ActiveRecord}
45
+ s.test_files = [
46
+ "test/acts_as_lookup_test.rb",
47
+ "test/helper.rb",
48
+ "test/test_hopsoft_acts_as_lookup.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ end
60
+ else
61
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
+ end
63
+ end
64
+
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Include hook code here
2
+ require 'hopsoft/acts_as_lookup'
3
+ ActiveRecord::Base.include(Hopsoft::ActsAsLookup)
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,287 @@
1
+ require 'activerecord'
2
+
3
+ # This plugin allows you to painlessly move away from column based lookups to normalized lookup tables.
4
+ #
5
+ # Simply add "acts_as_lookup" to your lookup table models.
6
+ #
7
+ # The assumed schema inclueds a "name" column which is used as the "key column".
8
+ # The "key column" is the column that holds the values that are used to key off of.
9
+ # The "key column" allows you to code against expected values in the database
10
+ # rather than hard coding with primary key values.
11
+ #
12
+ # This is generally safe considering that lookup data is relatively static.
13
+ #
14
+ # Consider the following schema:
15
+ # ----------------------------------------------------
16
+ # addresses
17
+ # * id
18
+ # * street
19
+ # * state_id
20
+ # * zip
21
+ #
22
+ # states
23
+ # * id
24
+ # * name => "UT, GA, IL, etc..."
25
+ # * description
26
+ # ----------------------------------------------------
27
+ #
28
+ # Here are some examples of what this plugin provides when working with a lookup table directly:
29
+ #
30
+ # Obtain id from a lookup table like so:
31
+ # utah_id = State.ut(:id)
32
+ #
33
+ # Obtain the description like so:
34
+ # utah_desc = State.ut(:description)
35
+ #
36
+ # The real power comes with models that contain "belongs_to" relationships to parents that act_as_lookup.
37
+ # These models implicitly mixin some additional behavior that make working with lookup tables
38
+ # as simple as using columns directly on the table itself.
39
+ #
40
+ # For example:
41
+ #
42
+ # You can select with the following syntax:
43
+ # Address.find_by_state("UT")
44
+ #
45
+ # You can also assign like so:
46
+ # addr = Address.new
47
+ # addr.state = "UT"
48
+ #
49
+ # If a state with "UT" doesn't exist in the states table, one will be implicitly created.
50
+ # The requirement being that the model validates correctly. In this case only a name is assigned.
51
+ module Hopsoft
52
+ module ActsAsLookup
53
+
54
+ def self.included(base)
55
+ base.extend ClassMethods
56
+ end
57
+
58
+ module ClassMethods
59
+
60
+ # Adds lookup table behavior to a model.
61
+ def acts_as_lookup(options={})
62
+ @key_column = options[:key_column] || :name
63
+ include Hopsoft::ActsAsLookup::IsLookup::InstanceMethods
64
+ extend Hopsoft::ActsAsLookup::IsLookup::StaticMethods
65
+ init
66
+ end
67
+
68
+ # Used to implicitly mixin additional behavior for models that contain "belongs_to"
69
+ # relationships to lookup tables.
70
+ def belongs_to(association_id, options={})
71
+ result = super
72
+
73
+ begin
74
+ parent_model_name = options[:class_name] || association_id.to_s.camelize
75
+ parent_model = Object.const_get(parent_model_name)
76
+ parent_acts_as_lookup = defined?(parent_model.key_column)
77
+
78
+ # only add the behavior for belongs_to relationships where the parent implements "acts_as_lookup".
79
+ if parent_acts_as_lookup
80
+ @lookup_models ||= []
81
+ @lookup_models << parent_model
82
+
83
+ unless @lookup_behavior_added
84
+ include Hopsoft::ActsAsLookup::UsesLookups::InstanceMethods
85
+ extend Hopsoft::ActsAsLookup::UsesLookups::StaticMethods
86
+ @lookup_behavior_added = true
87
+ end
88
+ end
89
+ rescue
90
+ puts("LookupTables plugin error! Unable to override belongs_to for '#{parent_model_name}'!\n#{$!}")
91
+ end
92
+
93
+ result
94
+ end
95
+ end
96
+
97
+ # This module is implicitly mixed into any models that specify "belongs_to" using an "acts_as_lookup" model.
98
+ # Provides some helpful tools for selecting and assigning values for lookup tables.
99
+ # See the plugin description for more details.
100
+ module UsesLookups
101
+
102
+ # Add class methods here
103
+ module StaticMethods
104
+ attr_reader :lookup_models
105
+
106
+ def method_missing(name, *args)
107
+ if name =~ /^find_by_/i && args && args.length == 1
108
+ @lookup_models.each do |model|
109
+ attribute_name = model.table_name.singularize
110
+
111
+ if name =~ /#{attribute_name}$/i
112
+ # if we get here, assume a lookup is being performed
113
+ return lookup_parent(model, args[0])
114
+ end
115
+ end
116
+ end
117
+
118
+ super
119
+ end
120
+
121
+ private
122
+
123
+ # Attempts to find a parent record based on the passed value.
124
+ # The parent model must implement "acts_as_lookup" for this method to work as expected.
125
+ #
126
+ # Exmaple:
127
+ # Message.find_by_message_status :pending
128
+ def lookup_parent(parent_model, value)
129
+ value = value.to_s.downcase.gsub(/ /, "_")
130
+ return send("find_by_#{parent_model.table_name.singularize}_id", parent_model.send(value, :id))
131
+ end
132
+
133
+ end
134
+
135
+ # Add instance methods here
136
+ module InstanceMethods
137
+
138
+ end
139
+
140
+ end
141
+
142
+ module IsLookup
143
+
144
+ # Add class methods here
145
+ module StaticMethods
146
+ attr_reader :key_column
147
+
148
+ def method_missing(name, *args)
149
+
150
+ if args && args.length == 1
151
+ column_name = args[0].to_s.downcase
152
+
153
+ if column_name =~ /^object$/
154
+ return get_record(name)
155
+ else
156
+ if @lookup_columns.include?(column_name)
157
+ # once here we are assuming they are performing a lookup
158
+ return get_column_value(name, column_name)
159
+ end
160
+ end
161
+ end
162
+
163
+ return super
164
+ end
165
+
166
+ # Gets a column's value from an "acts_as_lookup" model.
167
+ #
168
+ # ===Params
169
+ # * *value* - The "key column" value to find.
170
+ # * *column_name* - The column's name to return a value for.
171
+ def get_column_value(value, column_name)
172
+ record = get_record(value)
173
+ return record.attributes[column_name] if record
174
+ return nil
175
+ end
176
+
177
+ # Gets an ActiveRecord object from an "acts_as_lookup" model.
178
+ #
179
+ # ===Params
180
+ # * *value* - The "key column" value to find.
181
+ def get_record(value)
182
+ value = value.to_s.downcase.gsub(/_/, " ")
183
+ finder_method = "find_by_#{@key_column}"
184
+ item = send(finder_method, value)
185
+ item
186
+ end
187
+
188
+ # Initializes the plugin.
189
+ def init
190
+ @lookup_columns = new.attributes.keys.map {|attr_name| attr_name.downcase.gsub(/ /, "_") }
191
+ @lookup_columns << "id"
192
+ end
193
+
194
+ end
195
+
196
+ # Add instance methods here
197
+ module InstanceMethods
198
+
199
+ def to_s
200
+ return name
201
+ end
202
+
203
+ def ==(value)
204
+ return name == value if value.is_a?(String)
205
+ return name == value.to_s if value.is_a?(Symbol)
206
+ return id == value.id if self.class == value.class
207
+ return self.object_id == value.object_id
208
+ end
209
+
210
+ def =~(value)
211
+ return name =~ value if value.is_a?(Regexp)
212
+ return name =~ /#{value}/i if value.is_a?(String)
213
+ return name =~ /#{value.to_s}/i if value.is_a?(Symbol)
214
+ return false
215
+ end
216
+
217
+ end
218
+
219
+ end
220
+
221
+ end
222
+ end
223
+
224
+ module ActiveRecord
225
+ class Migration
226
+
227
+ # Migration helper for creating a standardized lookup table.
228
+ def self.create_lookup_table(name)
229
+ create_table name do |t|
230
+ # lookup columns
231
+ t.column :name, :string, :limit => 50, :null => false
232
+ t.column :description, :text
233
+ t.column :enabled, :boolean, :default => true, :null => false
234
+ t.column :sort_order, :integer, :default => 0, :null => false
235
+ end rescue
236
+ add_index(name, :name, :unique => true)
237
+ end
238
+
239
+ end
240
+
241
+ module Associations
242
+ class BelongsToAssociation
243
+ alias :orig_replace :replace
244
+
245
+ # Overriding "replace" to allow implicit lookup table insertions.
246
+ # The assumption being that the only required field is the specified "key column" or
247
+ # in other words the column whose value we use to key off of.
248
+ def replace(record)
249
+ if record.is_a?(Symbol) || record.is_a?(String)
250
+ value = record.to_s
251
+ attribute_name = proxy_reflection.table_name.singularize
252
+ lookup_models = proxy_owner.class.lookup_models
253
+
254
+ lookup_models.each do |model|
255
+ if attribute_name =~ /^#{model.table_name.singularize}$/i
256
+ record = model.get_record(value)
257
+
258
+ if record.nil?
259
+ new_record = model.new(model.key_column.to_sym => value)
260
+ record = new_record if new_record.save
261
+ end
262
+
263
+ break
264
+ end
265
+ end
266
+
267
+ end
268
+
269
+ return orig_replace(record)
270
+ end
271
+
272
+ end
273
+ end
274
+ end
275
+
276
+ module ApplicationHelper
277
+ def select_from_lookup(table, options = {})
278
+ if table.is_a?(Symbol) || table.is_a?(String)
279
+ model = Object.const_get(table.to_s.classify)
280
+ field ||= options[:field] || "name"
281
+ collection = model.send(:find, :all)
282
+ default ||= options[:value] || nil
283
+ choices = options_from_collection_for_select(collection, "name", field, default)
284
+ select_tag table, choices, options
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_lookup do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+
3
+ class ActsAsLookupTest < Test::Unit::TestCase
4
+
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'hopsoft_acts_as_lookup'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestHopsoftActsAsLookup < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hopsoft-acts-as-lookup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Hopkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-21 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Powerful lookup table behavior added to ActiveRecord.
26
+ email: natehop@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README
34
+ - README.rdoc
35
+ files:
36
+ - .document
37
+ - .gitignore
38
+ - LICENSE
39
+ - README
40
+ - README.rdoc
41
+ - Rakefile
42
+ - VERSION
43
+ - acts_as_lookup.gemspec
44
+ - hopsoft-acts-as-lookup-0.1.0.gem
45
+ - hopsoft-acts-as-lookup.gemspec
46
+ - init.rb
47
+ - install.rb
48
+ - lib/hopsoft/acts_as_lookup.rb
49
+ - tasks/acts_as_lookup_tasks.rake
50
+ - test/acts_as_lookup_test.rb
51
+ - test/helper.rb
52
+ - test/test_hopsoft_acts_as_lookup.rb
53
+ - uninstall.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/hopsoft/acts_as_lookup
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --charset=UTF-8
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.5
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Lookup tables made easy for ActiveRecord
82
+ test_files:
83
+ - test/acts_as_lookup_test.rb
84
+ - test/helper.rb
85
+ - test/test_hopsoft_acts_as_lookup.rb