lookup_by 0.1.0

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