rails_lookup 0.0.1

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/Manifest ADDED
@@ -0,0 +1,4 @@
1
+ README
2
+ Rakefile
3
+ lib/active_record/lookup.rb
4
+ Manifest
data/README ADDED
@@ -0,0 +1,139 @@
1
+ Lookup tables with ruby-on-rails
2
+ --------------------------------
3
+
4
+ By: Nimrod Priell
5
+
6
+ This gem adds an ActiveRecord macro to define memory-cached, dynamically growing, normalized lookup tables for entity 'type'-like objects. Or in plain English - if you want to have a table containing, say, ProductTypes which can grow with new types simply when you refer to them, and not keep the Product table containing a thousand repeating 'type="book"' entries - sit down and try to follow through.
7
+
8
+ Motivation
9
+ ----------
10
+
11
+ A [normalized DB][1] means that you want to keep types as separate tables, with foreign keys pointing from your main entity to its type. For instance, instead of
12
+ ID | car_name | car_type
13
+ 1 | Chevrolet Aveo | Compact
14
+ 2 | Ford Fiesta | Compact
15
+ 3 | BMW Z-5 | Sports
16
+
17
+ You want to have two tables:
18
+ ID | car_name | car_type_id
19
+ 1 | Chevrolet Aveo | 1
20
+ 2 | Ford Fiesta | 1
21
+ 3 | BMW Z-5 | 2
22
+
23
+ And
24
+
25
+ car_type_id | car_type_name
26
+ 1 | Compact
27
+ 2 | Sports
28
+
29
+ The pros/cons of a normalized DB can be discussed elsewhere. I'd just point out a denormalized solution is most useful in settings like [column oriented DBMSes][2]. For the rest of us folks using standard databases, we usually want to use lookups.
30
+
31
+ The usual way to do this with ruby on rails is
32
+ * Generate a CarType model using `rails generate model CarType name:string`
33
+ * Link between CarType and Car tables using `belongs_to` and `has_many`
34
+
35
+ Then to work with this you can transparently read the car type:
36
+
37
+ car = Car.all.first
38
+ car.car_type.name # returns "Compact"
39
+
40
+ Ruby does an awesome job of caching the results for you, so that you'll probably not hit the DB every time you get the same car type from different car objects.
41
+
42
+ You can even make this shorter, by defining a delegate to car_type_name from CarType:
43
+
44
+ *in car_type_name.rb*
45
+
46
+ delegate :name, :to => :car, :prefix => true
47
+
48
+ And now you can access this as
49
+
50
+ car.car_type_name
51
+
52
+ However, it's less pleasant to insert with this technique:
53
+
54
+ car.car_type.car_type_name = "Sports"
55
+ car.car_type.save!
56
+ #Now let's see what happened to the OTHER compact car
57
+ Car.all.second.car_type_name #Oops, returns "Sports"
58
+
59
+ Right, what are we doing? We should've used
60
+
61
+ car.update_attributes(car_type: CarType.find_or_create_by_name(name: "Sports"))
62
+
63
+ Okay. Probably want to shove that into its own method rather than have this repeated in the code several times. But you also need a helper method for creating cars that way…
64
+
65
+ Furthermore, ruby is good about caching, but it caches by the exact query used, and the cache expires after the controller action ends. You can configure more advanced caches, perhaps.
66
+
67
+ The thing is all this can get tedious if you use a normalized structure where you have 15 entities and each has at least one 'type-like' field. That's a whole lot of dangling Type objects. What you really want is an interface like this:
68
+
69
+ car.all.first
70
+ car.car_type #return "Compact"
71
+ car.car_type = "Sports" #No effect on car.all.second, just automatically use the second constant
72
+ car.car_type = "Sedan" #Magically create a new type
73
+
74
+ Oh, and it'll be nice if all of this is cached and you can define car types as constants (or symbols). You obviously still want to be able to run:
75
+
76
+ CarType.where (:id > 3) #Just an example of supposed "arbitrary" SQL involving a real live CarType class
77
+
78
+ But you wanna minimize generating these numerous type classes. If you're like me, you don't even want to see them lying around in app/model. Who cares about them?
79
+
80
+ I've looked thoroughly for a nice rails solution to this, but after failing to find one, I created my own rails metaprogramming hook.
81
+
82
+ Installation
83
+ ------------
84
+
85
+ The result of this hook is that you get the exact syntax described above, with only two lines of code (no extra classes or anything):
86
+ In your ActiveRecord object simply add
87
+
88
+ require 'active_record/lookup'
89
+ class Car < ActiveRecord::Base
90
+ #...
91
+ include ActiveRecord::Lookup
92
+ lookup :car_type
93
+ #...
94
+ end
95
+
96
+ That's it. the generated CarType class (which you won't see as a car_type.rb file, obviously, as it is generated in real-time), contains some nice methods to look into the cache as well: So you can call
97
+
98
+ CarType.id_for "Sports" #Returns 2
99
+ CarType.name_for 1 #Returns "Compact"
100
+
101
+ and you can still hack at the underlying ID for an object, if you need to:
102
+
103
+ car = car.all.first
104
+ car.car_type = "Sports"
105
+ car.car_type_id #Returns 2
106
+ car.car_type_id = 1
107
+ car.car_type #Returns "Compact"
108
+
109
+ The only remaining thing is to define your migrations for creating the actual database tables. After all, that's something you only want to do once and not every time this class loads, so this isn't the place for it. However, it's easy enough to create your own scaffolds so that
110
+
111
+ rails generate migration create_car_type_lookup_for_car
112
+
113
+ will automatically create the migration
114
+
115
+ class CreateCarTypeLookupForCar < ActiveRecord::Migration
116
+ def self.up
117
+ create_table :car_types do |t|
118
+ t.string :name
119
+ t.timestamps #Btw you can remove these, I don't much like them in type tables anyway
120
+ end
121
+
122
+ remove_column :cars, :car_type #Let's assume you have one of those now…
123
+ add_column :cars, :car_type_id, :integer #Maybe put not_null constraints here.
124
+ end
125
+
126
+ def self.down
127
+ drop_table :car_types
128
+ add_column :cars, :car_type, :string
129
+ remove_column :cars, :car_type_id
130
+ end
131
+ end
132
+
133
+ I'll let you work out the details for actually migrating the data yourself.
134
+
135
+ [1] http://en.wikipedia.org/wiki/Database_normalization
136
+ [2] http://en.wikipedia.org/wiki/Column-oriented_DBMS
137
+
138
+ I hope this helped you and saved a lot of time and frustration. Follow me on twitter: @nimrodpriell
139
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('rails_lookup', '0.0.1') do |s|
6
+ s.description = File.read(File.join(File.dirname(__FILE__), 'README'))
7
+ s.summary = "Lookup table macro for ActiveRecords"
8
+ s.url = "http://github.com/Nimster/RailsLookup/"
9
+ s.author = "Nimrod Priell"
10
+ s.email = "@nimrodpriell" #Twitter
11
+ s.ignore_pattern = ["tmp/*", "script/*"]
12
+ s.development_dependencies = []
13
+ end
14
+
15
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
16
+ =begin
17
+ spec = Gem::Specification.new do |s|
18
+ s.name = "rails_lookup"
19
+ s.requirements = [ 'Rails >= 3.0.7, Ruby >= 1.9.1' ]
20
+ s.version = "0.0.1"
21
+ s.homepage = ""
22
+ s.platform = "Gem::Platform::RUBY"
23
+ s.required_ruby_version = '>=1.9'
24
+ s.files = Dir['**/**']
25
+ s.executables = []
26
+ s.test_files = [] #Dir["test/test*.rb"]
27
+ s.has_rdoc = false
28
+ end
29
+ =end
@@ -0,0 +1,134 @@
1
+ # Author: Nimrod Priell (Twitter: @nimrodpriell)
2
+ #
3
+ # See README file or blog post for more.
4
+ # This is intended to be included into ActiveRecord entities: Simply
5
+ # include ActiveRecord::Lookup
6
+ # in your class that extends ActiveRecord::Base
7
+ module ActiveRecord
8
+ module Lookup
9
+ #These will be defined as class methods.
10
+ module ClassMethods
11
+ #We define only this "macro". In your ActiveRecord entity (say, Car), use
12
+ # lookup :car_type
13
+ # which will create Car#car_type, Car#car_type=, Car#car_type_id,
14
+ # Car#car_type_id= and CarType, an ActiveRecord with a name (String)
15
+ # attribute, that has_many :cars.
16
+ #
17
+ # You can also use
18
+ # lookup :car_type, :as => :type
19
+ # which will do the same thing but create Car#type, Car#type=, Car#type_id
20
+ # and Car#type_id= instead.
21
+ def lookup(lookup_name, opts = {})
22
+ as_name = opts[:as] || lookup_name
23
+ mycls = self #Class I'm defined in
24
+
25
+ #Create the new ActiveRecord for the lookup
26
+ cls = Class.new(ActiveRecord::Base) do
27
+ #It has_many of the containing class
28
+ has_many mycls.to_s.split(/[A-Z]/).map(&:downcase).join('_').to_sym
29
+
30
+ validates_uniqueness_of :name
31
+ validates :name, :presence => true
32
+
33
+ #Query the cache for the DB ID of a certain value
34
+ def self.id_for(name, id = nil)
35
+ class_variable_get(:@@rcaches)[name] ||= id
36
+ end
37
+
38
+ #This helper method is the "find_or_create" of the class that also
39
+ #updates the DB. I didn't want to overwrite the original
40
+ #find_or_create_by_name since it is linked with a lot of the rails
41
+ #behaviour and I want to avoid altering that.
42
+ def self.gen_id_for(val)
43
+ id = id_for val
44
+ if id.nil?
45
+ #Define this new possible value
46
+ new_db_obj = find_or_create_by_name val #You could just override this...
47
+ id_for val, new_db_obj.id
48
+ name_for new_db_obj.id, val
49
+ id = new_db_obj.id
50
+ end
51
+ id
52
+ end
53
+
54
+ #Query the cache for the value that goes with a certain DB ID
55
+ def self.name_for(id, name = nil)
56
+ class_variable_get(:@@caches)[id] ||= name
57
+ end
58
+ end
59
+
60
+ #Bind the created class to a name
61
+ lookup_cls_name = lookup_name.to_s.split("_").map(&:capitalize).join("")
62
+ Object.const_set lookup_cls_name, cls #Define it as a global class
63
+
64
+ #Define the setter by string value
65
+ define_method("#{as_name.to_s}=") do |val|
66
+ id = cls.gen_id_for val
67
+ write_attribute "#{as_name.to_s}_id".to_sym, id
68
+ end
69
+
70
+ #Define the getter
71
+ define_method("#{as_name.to_s}") do
72
+ id = read_attribute "@#{as_name.to_s}_id".to_sym
73
+ if not id.nil?
74
+ value = cls.name_for id
75
+ if value.nil?
76
+ lookup_obj = cls.find_by_id id
77
+ if not lookup_obj.nil?
78
+ cls.name_for id, lookup_obj.name
79
+ cls.id_for lookup_obj.name, id
80
+ end
81
+ end
82
+ end
83
+ value
84
+ end
85
+
86
+ #This sucks but it's the only way I could figure out to create the exact
87
+ #method_missing method signature and still pass it the values I need to
88
+ #use it. Maybe these should be class variables, but the best thing is if
89
+ #they could only be defined in this scope and carried along with the
90
+ #closure to the actual method.
91
+ @as_name = as_name
92
+ @cls = cls
93
+ #We need to wrap around rails' default finders so that
94
+ #find_by_car_type and similar methods will behave correctly. We translate
95
+ #them on the fly to find_by_car_type_id and give the id instead of the
96
+ #requested name.
97
+ def method_missing(method_id, *arguments, &block)
98
+ if match = ActiveRecord::DynamicFinderMatch.match(method_id)
99
+ #reroute car_type to _car_type_id
100
+ idx = match.attribute_names.find_index { |n| n == @as_name.to_s }
101
+ if not idx.nil?
102
+ self.new.send "#{@as_name}=", arguments[idx] #Make sure there's a cached value
103
+ arguments[idx] = @cls.id_for arguments[idx] #Change the argument
104
+ method_id = method_id.to_s.sub /(by|and)_#{@as_name}$/, "\\1_#{@as_name}_id"
105
+ method_id = method_id.to_s.sub /(by|and)_#{@as_name}_and/, "\\1_#{@as_name}_id_and"
106
+ method_id = method_id.to_sym
107
+ end
108
+ end
109
+ super
110
+ end
111
+
112
+ #Define the link between the host class and the newly created ActiveRecord
113
+ belongs_to lookup_name.to_s.to_sym, :foreign_key => "#{as_name}_id".to_sym
114
+ validates "#{as_name.to_s}_id".to_sym, :presence => true
115
+
116
+ #Prefill the hashes from the DB
117
+ all_vals = cls.all
118
+ cls.class_variable_set(:@@rcaches, all_vals.inject({}) do |r, obj|
119
+ r[obj.name] = obj.id
120
+ r
121
+ end)
122
+ cls.class_variable_set(:@@caches, all_vals.inject([]) do |r, obj|
123
+ r[obj.id] = obj.name
124
+ r
125
+ end)
126
+ end
127
+ end
128
+
129
+ # extend host class with class methods when we're included
130
+ def self.included(host_class)
131
+ host_class.extend(ClassMethods)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,168 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rails_lookup}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = [%q{Nimrod Priell}]
9
+ s.date = %q{2011-08-06}
10
+ s.description = %q{Lookup tables with ruby-on-rails
11
+ --------------------------------
12
+
13
+ By: Nimrod Priell
14
+
15
+ This gem adds an ActiveRecord macro to define memory-cached, dynamically growing, normalized lookup tables for entity 'type'-like objects. Or in plain English - if you want to have a table containing, say, ProductTypes which can grow with new types simply when you refer to them, and not keep the Product table containing a thousand repeating 'type="book"' entries - sit down and try to follow through.
16
+
17
+ Motivation
18
+ ----------
19
+
20
+ A [normalized DB][1] means that you want to keep types as separate tables, with foreign keys pointing from your main entity to its type. For instance, instead of
21
+ ID | car_name | car_type
22
+ 1 | Chevrolet Aveo | Compact
23
+ 2 | Ford Fiesta | Compact
24
+ 3 | BMW Z-5 | Sports
25
+
26
+ You want to have two tables:
27
+ ID | car_name | car_type_id
28
+ 1 | Chevrolet Aveo | 1
29
+ 2 | Ford Fiesta | 1
30
+ 3 | BMW Z-5 | 2
31
+
32
+ And
33
+
34
+ car_type_id | car_type_name
35
+ 1 | Compact
36
+ 2 | Sports
37
+
38
+ The pros/cons of a normalized DB can be discussed elsewhere. I'd just point out a denormalized solution is most useful in settings like [column oriented DBMSes][2]. For the rest of us folks using standard databases, we usually want to use lookups.
39
+
40
+ The usual way to do this with ruby on rails is
41
+ * Generate a CarType model using `rails generate model CarType name:string`
42
+ * Link between CarType and Car tables using `belongs_to` and `has_many`
43
+
44
+ Then to work with this you can transparently read the car type:
45
+
46
+ car = Car.all.first
47
+ car.car_type.name # returns "Compact"
48
+
49
+ Ruby does an awesome job of caching the results for you, so that you'll probably not hit the DB every time you get the same car type from different car objects.
50
+
51
+ You can even make this shorter, by defining a delegate to car_type_name from CarType:
52
+
53
+ *in car_type_name.rb*
54
+
55
+ delegate :name, :to => :car, :prefix => true
56
+
57
+ And now you can access this as
58
+
59
+ car.car_type_name
60
+
61
+ However, it's less pleasant to insert with this technique:
62
+
63
+ car.car_type.car_type_name = "Sports"
64
+ car.car_type.save!
65
+ #Now let's see what happened to the OTHER compact car
66
+ Car.all.second.car_type_name #Oops, returns "Sports"
67
+
68
+ Right, what are we doing? We should've used
69
+
70
+ car.update_attributes(car_type: CarType.find_or_create_by_name(name: "Sports"))
71
+
72
+ Okay. Probably want to shove that into its own method rather than have this repeated in the code several times. But you also need a helper method for creating cars that way…
73
+
74
+ Furthermore, ruby is good about caching, but it caches by the exact query used, and the cache expires after the controller action ends. You can configure more advanced caches, perhaps.
75
+
76
+ The thing is all this can get tedious if you use a normalized structure where you have 15 entities and each has at least one 'type-like' field. That's a whole lot of dangling Type objects. What you really want is an interface like this:
77
+
78
+ car.all.first
79
+ car.car_type #return "Compact"
80
+ car.car_type = "Sports" #No effect on car.all.second, just automatically use the second constant
81
+ car.car_type = "Sedan" #Magically create a new type
82
+
83
+ Oh, and it'll be nice if all of this is cached and you can define car types as constants (or symbols). You obviously still want to be able to run:
84
+
85
+ CarType.where (:id > 3) #Just an example of supposed "arbitrary" SQL involving a real live CarType class
86
+
87
+ But you wanna minimize generating these numerous type classes. If you're like me, you don't even want to see them lying around in app/model. Who cares about them?
88
+
89
+ I've looked thoroughly for a nice rails solution to this, but after failing to find one, I created my own rails metaprogramming hook.
90
+
91
+ Installation
92
+ ------------
93
+
94
+ The result of this hook is that you get the exact syntax described above, with only two lines of code (no extra classes or anything):
95
+ In your ActiveRecord object simply add
96
+
97
+ require 'active_record/lookup'
98
+ class Car < ActiveRecord::Base
99
+ #...
100
+ include ActiveRecord::Lookup
101
+ lookup :car_type
102
+ #...
103
+ end
104
+
105
+ That's it. the generated CarType class (which you won't see as a car_type.rb file, obviously, as it is generated in real-time), contains some nice methods to look into the cache as well: So you can call
106
+
107
+ CarType.id_for "Sports" #Returns 2
108
+ CarType.name_for 1 #Returns "Compact"
109
+
110
+ and you can still hack at the underlying ID for an object, if you need to:
111
+
112
+ car = car.all.first
113
+ car.car_type = "Sports"
114
+ car.car_type_id #Returns 2
115
+ car.car_type_id = 1
116
+ car.car_type #Returns "Compact"
117
+
118
+ The only remaining thing is to define your migrations for creating the actual database tables. After all, that's something you only want to do once and not every time this class loads, so this isn't the place for it. However, it's easy enough to create your own scaffolds so that
119
+
120
+ rails generate migration create_car_type_lookup_for_car
121
+
122
+ will automatically create the migration
123
+
124
+ class CreateCarTypeLookupForCar < ActiveRecord::Migration
125
+ def self.up
126
+ create_table :car_types do |t|
127
+ t.string :name
128
+ t.timestamps #Btw you can remove these, I don't much like them in type tables anyway
129
+ end
130
+
131
+ remove_column :cars, :car_type #Let's assume you have one of those now…
132
+ add_column :cars, :car_type_id, :integer #Maybe put not_null constraints here.
133
+ end
134
+
135
+ def self.down
136
+ drop_table :car_types
137
+ add_column :cars, :car_type, :string
138
+ remove_column :cars, :car_type_id
139
+ end
140
+ end
141
+
142
+ I'll let you work out the details for actually migrating the data yourself.
143
+
144
+ [1] http://en.wikipedia.org/wiki/Database_normalization
145
+ [2] http://en.wikipedia.org/wiki/Column-oriented_DBMS
146
+
147
+ I hope this helped you and saved a lot of time and frustration. Follow me on twitter: @nimrodpriell
148
+
149
+ }
150
+ s.email = %q{@nimrodpriell}
151
+ s.extra_rdoc_files = [%q{README}, %q{lib/active_record/lookup.rb}]
152
+ s.files = [%q{README}, %q{Rakefile}, %q{lib/active_record/lookup.rb}, %q{Manifest}, %q{rails_lookup.gemspec}]
153
+ s.homepage = %q{http://github.com/Nimster/RailsLookup/}
154
+ s.rdoc_options = [%q{--line-numbers}, %q{--inline-source}, %q{--title}, %q{Rails_lookup}, %q{--main}, %q{README}]
155
+ s.require_paths = [%q{lib}]
156
+ s.rubyforge_project = %q{rails_lookup}
157
+ s.rubygems_version = %q{1.8.7}
158
+ s.summary = %q{Lookup table macro for ActiveRecords}
159
+
160
+ if s.respond_to? :specification_version then
161
+ s.specification_version = 3
162
+
163
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
164
+ else
165
+ end
166
+ else
167
+ end
168
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_lookup
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Nimrod Priell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-06 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: "Lookup tables with ruby-on-rails\n\
17
+ --------------------------------\n\n\
18
+ By: Nimrod Priell\n\n\
19
+ This gem adds an ActiveRecord macro to define memory-cached, dynamically growing, normalized lookup tables for entity 'type'-like objects. Or in plain English - if you want to have a table containing, say, ProductTypes which can grow with new types simply when you refer to them, and not keep the Product table containing a thousand repeating 'type=\"book\"' entries - sit down and try to follow through.\n\n\
20
+ Motivation\n\
21
+ ----------\n\n\
22
+ A [normalized DB][1] means that you want to keep types as separate tables, with foreign keys pointing from your main entity to its type. For instance, instead of \n\
23
+ ID | car_name | car_type\n\
24
+ 1 | Chevrolet Aveo | Compact\n\
25
+ 2 | Ford Fiesta | Compact\n\
26
+ 3 | BMW Z-5 | Sports\n\n\
27
+ You want to have two tables:\n\
28
+ ID | car_name | car_type_id\n\
29
+ 1 | Chevrolet Aveo | 1\n\
30
+ 2 | Ford Fiesta | 1\n\
31
+ 3 | BMW Z-5 | 2\n\n\
32
+ And\n\n\
33
+ car_type_id | car_type_name \n\
34
+ 1 | Compact\n\
35
+ 2 | Sports\n\n\
36
+ The pros/cons of a normalized DB can be discussed elsewhere. I'd just point out a denormalized solution is most useful in settings like [column oriented DBMSes][2]. For the rest of us folks using standard databases, we usually want to use lookups.\n\n\
37
+ The usual way to do this with ruby on rails is\n\
38
+ * Generate a CarType model using `rails generate model CarType name:string`\n\
39
+ * Link between CarType and Car tables using `belongs_to` and `has_many`\n\n\
40
+ Then to work with this you can transparently read the car type:\n\n car = Car.all.first\n car.car_type.name # returns \"Compact\"\n\n\
41
+ Ruby does an awesome job of caching the results for you, so that you'll probably not hit the DB every time you get the same car type from different car objects.\n\n\
42
+ You can even make this shorter, by defining a delegate to car_type_name from CarType:\n\n\
43
+ *in car_type_name.rb*\n \n delegate :name, :to => :car, :prefix => true\n\n\
44
+ And now you can access this as \n\n car.car_type_name\n\n\
45
+ However, it's less pleasant to insert with this technique:\n\n car.car_type.car_type_name = \"Sports\"\n car.car_type.save!\n #Now let's see what happened to the OTHER compact car\n Car.all.second.car_type_name #Oops, returns \"Sports\"\n\n\
46
+ Right, what are we doing? We should've used\n\n car.update_attributes(car_type: CarType.find_or_create_by_name(name: \"Sports\"))\n\n\
47
+ Okay. Probably want to shove that into its own method rather than have this repeated in the code several times. But you also need a helper method for creating cars that way\xE2\x80\xA6\n\n\
48
+ Furthermore, ruby is good about caching, but it caches by the exact query used, and the cache expires after the controller action ends. You can configure more advanced caches, perhaps.\n\n\
49
+ The thing is all this can get tedious if you use a normalized structure where you have 15 entities and each has at least one 'type-like' field. That's a whole lot of dangling Type objects. What you really want is an interface like this:\n\n car.all.first\n car.car_type #return \"Compact\"\n car.car_type = \"Sports\" #No effect on car.all.second, just automatically use the second constant\n car.car_type = \"Sedan\" #Magically create a new type\n\n\
50
+ Oh, and it'll be nice if all of this is cached and you can define car types as constants (or symbols). You obviously still want to be able to run:\n\n CarType.where (:id > 3) #Just an example of supposed \"arbitrary\" SQL involving a real live CarType class\n\n\
51
+ But you wanna minimize generating these numerous type classes. If you're like me, you don't even want to see them lying around in app/model. Who cares about them?\n\n\
52
+ I've looked thoroughly for a nice rails solution to this, but after failing to find one, I created my own rails metaprogramming hook.\n\n\
53
+ Installation\n\
54
+ ------------\n\n\
55
+ The result of this hook is that you get the exact syntax described above, with only two lines of code (no extra classes or anything):\n\
56
+ In your ActiveRecord object simply add\n\n require 'active_record/lookup'\n class Car < ActiveRecord::Base\n #...\n include ActiveRecord::Lookup\n lookup :car_type\n #...\n end\n\n\
57
+ That's it. the generated CarType class (which you won't see as a car_type.rb file, obviously, as it is generated in real-time), contains some nice methods to look into the cache as well: So you can call\n\n CarType.id_for \"Sports\" #Returns 2\n CarType.name_for 1 #Returns \"Compact\"\n\n\
58
+ and you can still hack at the underlying ID for an object, if you need to:\n\n car = car.all.first\n car.car_type = \"Sports\"\n car.car_type_id #Returns 2\n car.car_type_id = 1\n car.car_type #Returns \"Compact\"\n\n\
59
+ The only remaining thing is to define your migrations for creating the actual database tables. After all, that's something you only want to do once and not every time this class loads, so this isn't the place for it. However, it's easy enough to create your own scaffolds so that \n\n rails generate migration create_car_type_lookup_for_car\n\n\
60
+ will automatically create the migration\n\n class CreateCarTypeLookupForCar < ActiveRecord::Migration\n def self.up\n create_table :car_types do |t|\n t.string :name\n t.timestamps #Btw you can remove these, I don't much like them in type tables anyway\n end\n \n remove_column :cars, :car_type #Let's assume you have one of those now\xE2\x80\xA6\n add_column :cars, :car_type_id, :integer #Maybe put not_null constraints here.\n end\n\n def self.down\n drop_table :car_types\n add_column :cars, :car_type, :string\n remove_column :cars, :car_type_id\n end\n end\n\n\
61
+ I'll let you work out the details for actually migrating the data yourself. \n\n\
62
+ [1] http://en.wikipedia.org/wiki/Database_normalization\n\
63
+ [2] http://en.wikipedia.org/wiki/Column-oriented_DBMS\n\n\
64
+ I hope this helped you and saved a lot of time and frustration. Follow me on twitter: @nimrodpriell\n\n"
65
+ email: "@nimrodpriell"
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files:
71
+ - README
72
+ - lib/active_record/lookup.rb
73
+ files:
74
+ - README
75
+ - Rakefile
76
+ - lib/active_record/lookup.rb
77
+ - Manifest
78
+ - rails_lookup.gemspec
79
+ homepage: http://github.com/Nimster/RailsLookup/
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --line-numbers
85
+ - --inline-source
86
+ - --title
87
+ - Rails_lookup
88
+ - --main
89
+ - README
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: "1.2"
104
+ requirements: []
105
+
106
+ rubyforge_project: rails_lookup
107
+ rubygems_version: 1.8.7
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Lookup table macro for ActiveRecords
111
+ test_files: []
112
+