hopsoft-acts-as-lookup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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