lookup_by 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.
Files changed (51) hide show
  1. data/.gitignore +11 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +16 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +230 -0
  7. data/Rakefile +14 -0
  8. data/TODO.md +16 -0
  9. data/lib/lookup_by/association.rb +89 -0
  10. data/lib/lookup_by/cache.rb +145 -0
  11. data/lib/lookup_by/caching/lru.rb +57 -0
  12. data/lib/lookup_by/caching/safe_lru.rb +30 -0
  13. data/lib/lookup_by/cucumber.rb +7 -0
  14. data/lib/lookup_by/hooks/cucumber.rb +9 -0
  15. data/lib/lookup_by/hooks/formtastic.rb +27 -0
  16. data/lib/lookup_by/hooks/simple_form.rb +27 -0
  17. data/lib/lookup_by/lookup.rb +113 -0
  18. data/lib/lookup_by/railtie.rb +20 -0
  19. data/lib/lookup_by/version.rb +3 -0
  20. data/lib/lookup_by.rb +27 -0
  21. data/lookup_by.gemspec +23 -0
  22. data/spec/association_spec.rb +102 -0
  23. data/spec/caching/lru_spec.rb +72 -0
  24. data/spec/dummy/.rspec +1 -0
  25. data/spec/dummy/Rakefile +14 -0
  26. data/spec/dummy/app/models/.gitkeep +0 -0
  27. data/spec/dummy/app/models/account.rb +5 -0
  28. data/spec/dummy/app/models/address.rb +6 -0
  29. data/spec/dummy/app/models/city.rb +5 -0
  30. data/spec/dummy/app/models/email_address.rb +5 -0
  31. data/spec/dummy/app/models/ip_address.rb +5 -0
  32. data/spec/dummy/app/models/postal_code.rb +5 -0
  33. data/spec/dummy/app/models/state.rb +5 -0
  34. data/spec/dummy/app/models/status.rb +9 -0
  35. data/spec/dummy/app/models/street.rb +5 -0
  36. data/spec/dummy/config/application.rb +20 -0
  37. data/spec/dummy/config/boot.rb +10 -0
  38. data/spec/dummy/config/database.yml +52 -0
  39. data/spec/dummy/config/environment.rb +5 -0
  40. data/spec/dummy/config/environments/development.rb +15 -0
  41. data/spec/dummy/config/environments/test.rb +16 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/db/migrate/20121019040009_create_tables.rb +23 -0
  44. data/spec/dummy/db/schema.rb +71 -0
  45. data/spec/dummy/lib/missing.rb +3 -0
  46. data/spec/dummy/log/.gitkeep +0 -0
  47. data/spec/dummy/script/rails +6 -0
  48. data/spec/lookup_by_spec.rb +100 -0
  49. data/spec/spec_helper.rb +43 -0
  50. data/spec/support/shared_examples_for_a_lookup.rb +163 -0
  51. metadata +140 -0
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20121019040009) do
15
+
16
+ create_table "accounts", :primary_key => "account_id", :force => true do |t|
17
+ t.text "account", :null => false
18
+ end
19
+
20
+ add_index "accounts", ["account"], :name => "index_accounts_on_account", :unique => true
21
+
22
+ create_table "addresses", :primary_key => "address_id", :force => true do |t|
23
+ t.integer "city_id"
24
+ t.integer "state_id"
25
+ t.integer "postal_code_id"
26
+ t.integer "street_id"
27
+ end
28
+
29
+ create_table "cities", :primary_key => "city_id", :force => true do |t|
30
+ t.text "city", :null => false
31
+ end
32
+
33
+ add_index "cities", ["city"], :name => "index_cities_on_city", :unique => true
34
+
35
+ create_table "email_addresses", :primary_key => "email_address_id", :force => true do |t|
36
+ t.text "email_address", :null => false
37
+ end
38
+
39
+ add_index "email_addresses", ["email_address"], :name => "index_email_addresses_on_email_address", :unique => true
40
+
41
+ create_table "ip_addresses", :primary_key => "ip_address_id", :force => true do |t|
42
+ t.text "ip_address", :null => false
43
+ end
44
+
45
+ add_index "ip_addresses", ["ip_address"], :name => "index_ip_addresses_on_ip_address", :unique => true
46
+
47
+ create_table "postal_codes", :primary_key => "postal_code_id", :force => true do |t|
48
+ t.text "postal_code", :null => false
49
+ end
50
+
51
+ add_index "postal_codes", ["postal_code"], :name => "index_postal_codes_on_postal_code", :unique => true
52
+
53
+ create_table "states", :primary_key => "state_id", :force => true do |t|
54
+ t.text "state", :null => false
55
+ end
56
+
57
+ add_index "states", ["state"], :name => "index_states_on_state", :unique => true
58
+
59
+ create_table "statuses", :primary_key => "status_id", :force => true do |t|
60
+ t.text "status", :null => false
61
+ end
62
+
63
+ add_index "statuses", ["status"], :name => "index_statuses_on_status", :unique => true
64
+
65
+ create_table "streets", :primary_key => "street_id", :force => true do |t|
66
+ t.text "street", :null => false
67
+ end
68
+
69
+ add_index "streets", ["street"], :name => "index_streets_on_street", :unique => true
70
+
71
+ end
@@ -0,0 +1,3 @@
1
+ class Missing < ActiveRecord::Base
2
+ lookup_for :city
3
+ end
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,100 @@
1
+ require "spec_helper"
2
+ require "lookup_by"
3
+ require "pry"
4
+
5
+ describe ::ActiveRecord::Base do
6
+ describe "macro methods" do
7
+ subject { described_class }
8
+
9
+ it { should respond_to :lookup_by }
10
+ it { should respond_to :is_a_lookup? }
11
+ end
12
+
13
+ describe "instance methods" do
14
+ subject { Status.new }
15
+
16
+ it { should respond_to :name }
17
+ end
18
+ end
19
+
20
+ describe LookupBy::Lookup do
21
+ context "City.lookup_by :column" do
22
+ subject { City }
23
+
24
+ it_behaves_like "a lookup"
25
+ it_behaves_like "a proxy"
26
+ it_behaves_like "a read-through proxy"
27
+
28
+ it "returns nil on db miss" do
29
+ subject["foo"].should be_nil
30
+ end
31
+ end
32
+
33
+ context "Status.lookup_by :column, normalize: true" do
34
+ subject { Status }
35
+
36
+ it_behaves_like "a lookup"
37
+ it_behaves_like "a proxy"
38
+ it_behaves_like "a read-through proxy"
39
+
40
+ it "normalizes the lookup field" do
41
+ status = subject.create(subject.lookup.field => "paid")
42
+
43
+ subject[" paid "].id.should == status.id
44
+ end
45
+ end
46
+
47
+ context "EmailAddress.lookup_by :column, find_or_create: true" do
48
+ subject { EmailAddress }
49
+
50
+ it_behaves_like "a lookup"
51
+ it_behaves_like "a proxy"
52
+ it_behaves_like "a read-through proxy"
53
+ it_behaves_like "a write-through proxy"
54
+ end
55
+
56
+ context "State.lookup_by :column, cache: true" do
57
+ subject { State }
58
+
59
+ it_behaves_like "a lookup"
60
+ it_behaves_like "a cache"
61
+ it_behaves_like "a strict cache"
62
+
63
+ it "preloads the cache" do
64
+ subject.lookup.cache.should_not be_empty
65
+ end
66
+ end
67
+
68
+ context "Account.lookup_by :column, cache: true, strict: false" do
69
+ subject { Account }
70
+
71
+ it_behaves_like "a lookup"
72
+ it_behaves_like "a cache"
73
+ it_behaves_like "a read-through cache"
74
+ end
75
+
76
+ context "PostalCode.lookup_by :column, cache: N" do
77
+ subject { PostalCode }
78
+
79
+ it_behaves_like "a lookup"
80
+ it_behaves_like "a cache"
81
+ it_behaves_like "a read-through cache"
82
+
83
+ it "enables the LRU" do
84
+ subject.lookup.enabled.should be_true
85
+ end
86
+ end
87
+
88
+ context "IpAddress.lookup_by :column, cache: N, find_or_create: true" do
89
+ subject { IpAddress }
90
+
91
+ it_behaves_like "a lookup"
92
+ it_behaves_like "a cache"
93
+ it_behaves_like "a read-through cache"
94
+ it_behaves_like "a write-through cache"
95
+
96
+ it "disables the LRU when RAILS_ENV=test" do
97
+ subject.lookup.enabled.should be_false
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,43 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ ENV["RAILS_ENV"] ||= 'test'
3
+
4
+ GC.disable if defined?(GC) && GC.respond_to?(:disable)
5
+
6
+ if ENV["COV"]
7
+ require "simplecov"
8
+ SimpleCov.start
9
+ end
10
+
11
+ begin
12
+ require File.expand_path("../../config/environment", __FILE__)
13
+ rescue LoadError
14
+ require File.expand_path("../dummy/config/environment", __FILE__)
15
+ end
16
+
17
+ require 'rspec/rails'
18
+ require 'rspec/autorun'
19
+
20
+ # Requires supporting ruby files with custom matchers and macros, etc,
21
+ # in spec/support/ and its subdirectories.
22
+ Dir[Rails.root.join("../support/**/*.rb")].each {|f| require f}
23
+
24
+ RSpec.configure do |config|
25
+ # ## Mock Framework
26
+ #
27
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
28
+ #
29
+ # config.mock_with :mocha
30
+ # config.mock_with :flexmock
31
+ # config.mock_with :rr
32
+
33
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
34
+ # examples within a transaction, remove the following line or assign false
35
+ # instead of true.
36
+ config.use_transactional_fixtures = true
37
+
38
+ # Run specs in random order to surface order dependencies. If you find an
39
+ # order dependency and want to debug it, you can fix the order by providing
40
+ # the seed, which is printed after each run.
41
+ # --seed 1234
42
+ config.order = "random"
43
+ end
@@ -0,0 +1,163 @@
1
+ shared_examples "a lookup" do
2
+ it { should respond_to :[] }
3
+ it { should respond_to :lookup }
4
+ it { should respond_to :lookup_by }
5
+ it { should respond_to :lookup_for }
6
+ its(:is_a_lookup?) { should be_true }
7
+
8
+ it "returns nil for nil" do
9
+ subject[nil].should be_nil
10
+ end
11
+
12
+ it "returns nil for empty strings" do
13
+ subject[""].should be_nil
14
+ end
15
+
16
+ it "returns itself" do
17
+ first = subject.first
18
+ subject[first].should eq first if first
19
+ end
20
+
21
+ it "rejects other argument types" do
22
+ [1.00, true, false, Address.new].each do |value|
23
+ expect { subject[value] }.to raise_error TypeError
24
+ end
25
+ end
26
+
27
+ it "proxies create!" do
28
+ expect { subject.lookup.create!(name: "add to cache") }.to change(subject, :count).by(1)
29
+ end
30
+ end
31
+
32
+ shared_examples "a proxy" do
33
+ it "does not cache records" do
34
+ original = subject.create(name: "original")
35
+
36
+ subject.lookup.reload
37
+
38
+ subject[original.name]
39
+ subject.update(original.id, name: "updated")
40
+ subject[original.id].name.should_not eq original.name
41
+ end
42
+ end
43
+
44
+ shared_examples "a cache" do
45
+ it "caches records" do
46
+ was_enabled = subject.lookup.enabled
47
+ subject.lookup.enabled = true
48
+
49
+ original = subject.create(name: "original")
50
+
51
+ subject.lookup.reload
52
+ subject[original.name]
53
+
54
+ subject.update(original.id, subject.lookup.field => "updated")
55
+ subject[original.id].name.should eq "original"
56
+
57
+ subject.lookup.enabled = was_enabled
58
+ end
59
+ end
60
+
61
+ shared_examples "a strict cache" do
62
+ it "caches .count" do
63
+ expect { subject.create(name: "new") }.to_not change(subject, :count)
64
+ end
65
+
66
+ it "caches .all" do
67
+ expect { subject.create(name: "add") }.to_not change(subject, :all)
68
+ end
69
+
70
+ it "caches .pluck" do
71
+ subject.create(name: "pluck this")
72
+ subject.pluck(:name).should_not include("pluck this")
73
+ end
74
+
75
+ it "returns nil on miss" do
76
+ subject["foo"].should be_nil
77
+ end
78
+
79
+ it "ignores new records" do
80
+ subject.create(name: "new record")
81
+
82
+ subject["new record"].should be_nil
83
+ end
84
+ end
85
+
86
+ shared_examples "a read-through proxy" do
87
+ it "reloads .count" do
88
+ expect { subject.create(name: "new") }.to change(subject, :count)
89
+ end
90
+
91
+ it "reloads .all" do
92
+ expect { subject.create(name: "add") }.to change(subject, :all)
93
+ end
94
+
95
+ it "reloads .pluck" do
96
+ subject.create(name: "pluck this")
97
+ subject.pluck(subject.lookup.field).should include("pluck this")
98
+ end
99
+
100
+ it "finds new records" do
101
+ created = subject.create(name: "new record")
102
+ subject["new record"].id.should eq created.id
103
+ end
104
+ end
105
+
106
+ shared_examples "a read-through cache" do
107
+ it_behaves_like "a read-through proxy"
108
+
109
+ it "caches new records" do
110
+ was_enabled = subject.lookup.enabled
111
+ subject.lookup.enabled = true
112
+
113
+ created = subject.create(name: "cached")
114
+
115
+ subject.lookup.reload
116
+ subject[created.name]
117
+
118
+ subject.update(created.id, name: "changed")
119
+ subject[created.id].name.should eq "cached"
120
+
121
+ subject.lookup.enabled = was_enabled
122
+ end
123
+ end
124
+
125
+ shared_examples "a write-through proxy" do
126
+ it "creates missing records" do
127
+ expect { subject["not found"] }.to change(subject, :count)
128
+ end
129
+ end
130
+
131
+ shared_examples "a write-through cache" do
132
+ it_behaves_like "a write-through proxy"
133
+
134
+ it "does not cache new records" do
135
+ subject.lookup.reload
136
+ found = subject["found"]
137
+
138
+ subject.update(found.id, name: "missing")
139
+ subject[found.id].name.should eq "missing"
140
+ end
141
+ end
142
+
143
+ shared_examples "a lookup for" do |field|
144
+ it { should respond_to field }
145
+ it { should respond_to "#{field}=" }
146
+ it { should respond_to "raw_#{field}" }
147
+ it { should respond_to "#{field}_before_type_cast" }
148
+
149
+ it "accepts nil" do
150
+ expect { subject.send "#{field}=", nil }.to_not raise_error
151
+ end
152
+
153
+ it "converts empty strings to nil" do
154
+ subject.send "#{field}=", ""
155
+ subject.send(field).should be_nil
156
+ end
157
+
158
+ it "rejects other argument types" do
159
+ [1.00, true, false, Address.new].each do |value|
160
+ expect { subject.send "#{field}=", value }.to raise_error TypeError
161
+ end
162
+ end
163
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lookup_by
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Erik Peterson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ none: false
21
+ name: rails
22
+ type: :runtime
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: 3.0.0
29
+ none: false
30
+ description: Use database lookup tables in AR models.
31
+ email:
32
+ - erik@enova.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rvmrc
39
+ - .travis.yml
40
+ - Gemfile
41
+ - MIT-LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ - TODO.md
45
+ - lib/lookup_by.rb
46
+ - lib/lookup_by/association.rb
47
+ - lib/lookup_by/cache.rb
48
+ - lib/lookup_by/caching/lru.rb
49
+ - lib/lookup_by/caching/safe_lru.rb
50
+ - lib/lookup_by/cucumber.rb
51
+ - lib/lookup_by/hooks/cucumber.rb
52
+ - lib/lookup_by/hooks/formtastic.rb
53
+ - lib/lookup_by/hooks/simple_form.rb
54
+ - lib/lookup_by/lookup.rb
55
+ - lib/lookup_by/railtie.rb
56
+ - lib/lookup_by/version.rb
57
+ - lookup_by.gemspec
58
+ - spec/association_spec.rb
59
+ - spec/caching/lru_spec.rb
60
+ - spec/dummy/.rspec
61
+ - spec/dummy/Rakefile
62
+ - spec/dummy/app/models/.gitkeep
63
+ - spec/dummy/app/models/account.rb
64
+ - spec/dummy/app/models/address.rb
65
+ - spec/dummy/app/models/city.rb
66
+ - spec/dummy/app/models/email_address.rb
67
+ - spec/dummy/app/models/ip_address.rb
68
+ - spec/dummy/app/models/postal_code.rb
69
+ - spec/dummy/app/models/state.rb
70
+ - spec/dummy/app/models/status.rb
71
+ - spec/dummy/app/models/street.rb
72
+ - spec/dummy/config.ru
73
+ - spec/dummy/config/application.rb
74
+ - spec/dummy/config/boot.rb
75
+ - spec/dummy/config/database.yml
76
+ - spec/dummy/config/environment.rb
77
+ - spec/dummy/config/environments/development.rb
78
+ - spec/dummy/config/environments/test.rb
79
+ - spec/dummy/db/migrate/20121019040009_create_tables.rb
80
+ - spec/dummy/db/schema.rb
81
+ - spec/dummy/lib/missing.rb
82
+ - spec/dummy/log/.gitkeep
83
+ - spec/dummy/script/rails
84
+ - spec/lookup_by_spec.rb
85
+ - spec/spec_helper.rb
86
+ - spec/support/shared_examples_for_a_lookup.rb
87
+ homepage: http://www.github.com/companygardener/lookup_by
88
+ licenses: []
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ none: false
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ none: false
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.23
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: A thread-safe lookup table cache for ActiveRecord
111
+ test_files:
112
+ - spec/association_spec.rb
113
+ - spec/caching/lru_spec.rb
114
+ - spec/dummy/.rspec
115
+ - spec/dummy/Rakefile
116
+ - spec/dummy/app/models/.gitkeep
117
+ - spec/dummy/app/models/account.rb
118
+ - spec/dummy/app/models/address.rb
119
+ - spec/dummy/app/models/city.rb
120
+ - spec/dummy/app/models/email_address.rb
121
+ - spec/dummy/app/models/ip_address.rb
122
+ - spec/dummy/app/models/postal_code.rb
123
+ - spec/dummy/app/models/state.rb
124
+ - spec/dummy/app/models/status.rb
125
+ - spec/dummy/app/models/street.rb
126
+ - spec/dummy/config.ru
127
+ - spec/dummy/config/application.rb
128
+ - spec/dummy/config/boot.rb
129
+ - spec/dummy/config/database.yml
130
+ - spec/dummy/config/environment.rb
131
+ - spec/dummy/config/environments/development.rb
132
+ - spec/dummy/config/environments/test.rb
133
+ - spec/dummy/db/migrate/20121019040009_create_tables.rb
134
+ - spec/dummy/db/schema.rb
135
+ - spec/dummy/lib/missing.rb
136
+ - spec/dummy/log/.gitkeep
137
+ - spec/dummy/script/rails
138
+ - spec/lookup_by_spec.rb
139
+ - spec/spec_helper.rb
140
+ - spec/support/shared_examples_for_a_lookup.rb