lookup_by 0.2.0 → 0.3.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/Rakefile +0 -1
  5. data/lib/lookup_by.rb +3 -0
  6. data/lib/lookup_by/association.rb +9 -4
  7. data/lib/lookup_by/cache.rb +30 -15
  8. data/lib/lookup_by/lookup.rb +42 -13
  9. data/lib/lookup_by/version.rb +1 -1
  10. data/spec/dummy/Rakefile +8 -0
  11. data/spec/dummy/app/models/account.rb +0 -2
  12. data/spec/dummy/app/models/city.rb +0 -2
  13. data/spec/dummy/app/models/email_address.rb +0 -2
  14. data/spec/dummy/app/models/ip_address.rb +0 -2
  15. data/spec/dummy/app/models/path.rb +5 -0
  16. data/spec/dummy/app/models/postal_code.rb +0 -2
  17. data/spec/dummy/app/models/state.rb +0 -2
  18. data/spec/dummy/app/models/status.rb +0 -2
  19. data/spec/dummy/app/models/street.rb +0 -2
  20. data/spec/dummy/app/models/user_agent.rb +3 -0
  21. data/spec/dummy/bin/bundle +3 -0
  22. data/spec/dummy/bin/rails +4 -0
  23. data/spec/dummy/bin/rake +4 -0
  24. data/spec/dummy/config/application.rb +18 -10
  25. data/spec/dummy/config/boot.rb +4 -8
  26. data/spec/dummy/config/environment.rb +2 -2
  27. data/spec/dummy/config/environments/development.rb +19 -5
  28. data/spec/dummy/config/environments/production.rb +80 -0
  29. data/spec/dummy/config/environments/test.rb +25 -5
  30. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  31. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  32. data/spec/dummy/config/initializers/inflections.rb +16 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  35. data/spec/dummy/config/initializers/session_store.rb +3 -0
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/spec/dummy/config/locales/en.yml +23 -0
  38. data/spec/dummy/config/routes.rb +56 -0
  39. data/spec/dummy/db/migrate/20121019040009_create_tables.rb +9 -6
  40. data/spec/dummy/db/seeds.rb +1 -0
  41. data/spec/dummy/db/structure.sql +616 -0
  42. data/spec/lookup_by_spec.rb +29 -2
  43. data/spec/spec_helper.rb +14 -1
  44. data/spec/support/shared_examples_for_a_lookup.rb +10 -8
  45. metadata +35 -3
  46. data/spec/dummy/db/schema.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c32a15a02ac5c67549366075fac6d76db2122564
4
- data.tar.gz: 07aa69751c19f10a96b573eb08d562e7290aee47
3
+ metadata.gz: 89c6960058f0ae2cbe8cb5654c37b331b0b25bd1
4
+ data.tar.gz: d13f7ed9c8e34d40b51e8c68983d19d9b5645cbc
5
5
  SHA512:
6
- metadata.gz: 14f87c363d0c346dafb9f37a1b015a721786c0ba4223ddfa1bb656adfe0dbf1d9764ea3724ac3330b336719382c5f014ae393b97f0c6eeeff3a936844d825af5
7
- data.tar.gz: c1cca0a1500aedf3a1f10ffaceca6dbe5d1fd870b86c9a0b23858f5c075aeaf6901c84f0fd73559a31c60a5cc64149c2f1925af69ab57935fa56d623f8550662
6
+ metadata.gz: f09941d8870cde958762337e6f75d42bf88c44d37d06fe63f7fc91f60e4c79fc8b4c4cb183de0403dc02f0a43587e06f75f65ff43d0b3eea8297f9ef023839b1
7
+ data.tar.gz: 4e7c909cd63d81350726fc80066e211d92cdf8c5c447a78ae8830f6dcd5cbc7f2706c758d52a430ecf3cc0475e51ed674dba2ae030701dd12eedf83730698da2
@@ -7,4 +7,4 @@ rvm:
7
7
  services:
8
8
  - postgresql
9
9
  before_script:
10
- - sh -c "cd spec/dummy && RAILS_ENV=test bundle exec rake db:migrate:reset"
10
+ - sh -c "cd spec/dummy && RAILS_ENV=test bundle exec rake db:migrate:reset db:seed"
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ group :development, :test do
10
10
  gem "rake"
11
11
  gem "simplecov", require: false
12
12
  gem "rspec-rails", "~> 2.11.0"
13
+ gem 'database_cleaner'
13
14
 
14
15
  gem "pg", platform: :ruby
15
16
  gem "activerecord-jdbcpostgresql-adapter", platform: :jruby
data/Rakefile CHANGED
@@ -11,4 +11,3 @@ require "rspec/core/rake_task"
11
11
  RSpec::Core::RakeTask.new(:spec)
12
12
 
13
13
  task :default => :spec
14
-
@@ -4,6 +4,9 @@ require "lookup_by/railtie" if defined? Rails
4
4
  module LookupBy
5
5
  class Error < StandardError; end
6
6
 
7
+ UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\Z/
8
+ UUID_REGEX_V4 = /\A\h{8}-\h{4}-4\h{3}-[89aAbB]\h{3}-\h{12}\Z/
9
+
7
10
  autoload :Association, "lookup_by/association"
8
11
  autoload :Cache, "lookup_by/cache"
9
12
  autoload :Lookup, "lookup_by/lookup"
@@ -14,7 +14,12 @@ module LookupBy
14
14
  module Association
15
15
  module MacroMethods
16
16
  def lookup_for field, options = {}
17
- return unless table_exists?
17
+ begin
18
+ return unless table_exists?
19
+ rescue => error
20
+ Rails.logger.error "lookup_by caught #{error.class.name} when connecting - skipping initialization (#{error.inspect})"
21
+ return
22
+ end
18
23
 
19
24
  options.symbolize_keys!
20
25
  options.assert_valid_keys(:class_name, :foreign_key, :symbolize, :strict, :scope)
@@ -84,9 +89,9 @@ module LookupBy
84
89
 
85
90
  def #{field}=(arg)
86
91
  value = case arg
87
- when "", nil
92
+ when nil
88
93
  nil
89
- when String, Integer
94
+ when String, Integer, IPAddr
90
95
  #{class_name}[arg].try(:id)
91
96
  when Symbol
92
97
  #{%Q(raise ArgumentError, "#{foreign_key}=(Symbol): use `lookup_for :column, symbolize: true` to allow symbols") unless options[:symbolize]}
@@ -95,7 +100,7 @@ module LookupBy
95
100
  raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.id
96
101
  arg.id
97
102
  else
98
- raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol, Integer, nil, or #{class_name}"
103
+ raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol, Integer, IPAddr, nil, or #{class_name}"
99
104
  end
100
105
 
101
106
  #{%Q(raise LookupBy::Error, "\#{arg.inspect} is not in the <#{class_name}> lookup cache" if arg.present? && value.nil?) if strict}
@@ -4,18 +4,20 @@ module LookupBy
4
4
  attr_accessor :testing
5
5
 
6
6
  def initialize(klass, options = {})
7
- @klass = klass
8
- @primary_key = klass.primary_key
9
- @field = options[:field].to_sym
10
- @cache = {}
11
- @order = options[:order] || @field
12
- @read = options[:find]
13
- @write = options[:find_or_create]
14
- @normalize = options[:normalize]
15
- @testing = false
16
- @enabled = true
17
-
18
- @stats = { db: Hash.new(0), cache: Hash.new(0) }
7
+ @klass = klass
8
+ @primary_key = klass.primary_key
9
+ @primary_key_type = klass.columns_hash[@primary_key].type
10
+ @field = options[:field].to_sym
11
+ @cache = {}
12
+ @order = options[:order] || @field
13
+ @read = options[:find_or_create] || options[:find]
14
+ @write = options[:find_or_create]
15
+ @allow_blank = options[:allow_blank] || false
16
+ @normalize = options[:normalize]
17
+ @testing = false
18
+ @enabled = true
19
+
20
+ @stats = { db: Hash.new(0), cache: Hash.new(0) }
19
21
 
20
22
  raise ArgumentError, %Q(unknown attribute "#{@field}" for <#{klass}>) unless klass.column_names.include?(@field.to_s)
21
23
 
@@ -62,7 +64,7 @@ module LookupBy
62
64
  def fetch(value)
63
65
  increment :cache, :get
64
66
 
65
- value = normalize(value) if @normalize && !value.is_a?(Integer)
67
+ value = normalize(value) if @normalize && !primary_key?(value)
66
68
 
67
69
  found = cache_read(value) if cache?
68
70
  found ||= db_read(value) if @read
@@ -83,6 +85,10 @@ module LookupBy
83
85
  @read
84
86
  end
85
87
 
88
+ def allow_blank?
89
+ @allow_blank
90
+ end
91
+
86
92
  def enabled?
87
93
  @enabled
88
94
  end
@@ -103,12 +109,21 @@ module LookupBy
103
109
 
104
110
  private
105
111
 
112
+ def primary_key?(value)
113
+ case @primary_key_type
114
+ when :integer
115
+ value.is_a? Integer
116
+ when :uuid
117
+ value =~ UUID_REGEX
118
+ end
119
+ end
120
+
106
121
  def normalize(value)
107
122
  @klass.new(@field => value).send(@field)
108
123
  end
109
124
 
110
125
  def cache_read(value)
111
- if value.is_a? Integer
126
+ if primary_key?(value)
112
127
  found = @cache[value]
113
128
  else
114
129
  found = @cache.values.detect { |o| o.send(@field) == value }
@@ -138,7 +153,7 @@ module LookupBy
138
153
  end
139
154
 
140
155
  def column_for(value)
141
- value.is_a?(Integer) ? @primary_key : @field
156
+ primary_key?(value) ? @primary_key : @field
142
157
  end
143
158
 
144
159
  def cache?
@@ -18,8 +18,15 @@ module LookupBy
18
18
  end
19
19
 
20
20
  def lookup_by(field, options = {})
21
+ begin
22
+ connection
23
+ rescue => error
24
+ Rails.logger.error "lookup_by caught #{error.class.name} when connecting - skipping initialization (#{error.inspect})"
25
+ return
26
+ end
27
+
21
28
  options.symbolize_keys!
22
- options.assert_valid_keys :order, :cache, :normalize, :find, :find_or_create, :raise
29
+ options.assert_valid_keys :allow_blank, :order, :cache, :normalize, :find, :find_or_create, :raise
23
30
 
24
31
  raise "#{self} already called lookup_by" if is_a? Lookup::ClassMethods
25
32
  raise "#{self} responds_to :[], needed for lookup_by" if respond_to? :[]
@@ -51,7 +58,9 @@ module LookupBy
51
58
  end
52
59
 
53
60
  module ClassMethods
61
+ # TODO: Rails 4 needs to return a proxy object here
54
62
  def all(*args)
63
+ return super if Rails::VERSION::MAJOR > 3
55
64
  return super if @lookup.read_through?
56
65
  return super if args.any?
57
66
 
@@ -76,10 +85,12 @@ module LookupBy
76
85
  when 0 then raise ArgumentError, "#{name}[*args]: at least one argument is required"
77
86
  when 1
78
87
  case arg = args.first
79
- when nil, "" then nil
88
+ when nil then nil
89
+ when "" then @lookup.allow_blank? ? @lookup.fetch(arg) : nil
80
90
  when String then @lookup.fetch(arg)
81
- when Symbol then @lookup.fetch(arg.to_s)
82
91
  when Integer then @lookup.fetch(arg)
92
+ when Symbol then @lookup.fetch(arg.to_s)
93
+ when IPAddr then @lookup.fetch(arg.to_s)
83
94
  when self then arg
84
95
  else raise TypeError, "#{name}[arg]: arg must be at least one String, Symbol, Integer, nil, or #{name}"
85
96
  end
@@ -91,7 +102,7 @@ module LookupBy
91
102
  module InstanceMethods
92
103
  def ===(arg)
93
104
  case arg
94
- when Symbol, String, Integer, nil
105
+ when Symbol, String, Integer, IPAddr, nil
95
106
  return self == self.class[arg]
96
107
  when Array
97
108
  return arg.any? { |i| self === i }
@@ -102,22 +113,40 @@ module LookupBy
102
113
  end
103
114
 
104
115
  module SchemaMethods
105
- def create_lookup_table(table_name, options = {})
106
- lookup_column = options[:lookup_column] || table_name.to_s.singularize
107
- primary_key = options[:primary_key] || table_name.to_s.singularize + "_id"
116
+ def create_lookup_table(name, options = {})
117
+ options.symbolize_keys!
118
+
119
+ schema = options[:schema].to_s
108
120
 
109
- create_table table_name, primary_key: primary_key do |t|
110
- t.text lookup_column, null: false
121
+ if schema.present?
122
+ table = name.to_s
123
+ else
124
+ schema, table = name.to_s.split('.')
125
+ schema, table = nil, schema unless table
126
+ end
127
+
128
+ name = schema.blank? ? table : "#{schema}.#{table}"
129
+
130
+ lookup_column = options[:lookup_column] || table.singularize
131
+ lookup_type = options[:lookup_type] || :text
132
+
133
+ table_options = options.slice(:primary_key, :id)
134
+ table_options[:primary_key] ||= table.singularize + '_id'
135
+
136
+ create_table name, table_options do |t|
137
+ t.send lookup_type, lookup_column, null: false
111
138
 
112
139
  yield t if block_given?
113
140
  end
114
141
 
115
- add_index table_name, lookup_column, unique: true
142
+ add_index name, lookup_column, unique: true, name: "#{table}__u_#{lookup_column}"
116
143
  end
117
144
 
118
- def create_lookup_tables(*table_names)
119
- table_names.each do |table_name|
120
- create_lookup_table table_name
145
+ def create_lookup_tables(*names)
146
+ options = names.last.is_a?(Hash) ? names.pop : {}
147
+
148
+ names.each do |name|
149
+ create_lookup_table name, options
121
150
  end
122
151
  end
123
152
  end
@@ -1,3 +1,3 @@
1
1
  module LookupBy
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -12,3 +12,11 @@ RSpec::Core::RakeTask.new(:spec) do |config|
12
12
  end
13
13
 
14
14
  task :default => :spec
15
+
16
+ namespace :db do
17
+ namespace :test do
18
+ task :load_structure => :environment do
19
+ Rake::Task["db:seed"].invoke
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,3 @@
1
1
  class Account < ActiveRecord::Base
2
- attr_accessible :account
3
-
4
2
  lookup_by :account, cache: true, find: true
5
3
  end
@@ -1,5 +1,3 @@
1
1
  class City < ActiveRecord::Base
2
- attr_accessible :city
3
-
4
2
  lookup_by :city
5
3
  end
@@ -1,5 +1,3 @@
1
1
  class EmailAddress < ActiveRecord::Base
2
- attr_accessible :email_address
3
-
4
2
  lookup_by :email_address, find_or_create: true
5
3
  end
@@ -1,5 +1,3 @@
1
1
  class IpAddress < ActiveRecord::Base
2
- attr_accessible :ip_address
3
-
4
2
  lookup_by :ip_address, cache: 2, find_or_create: true
5
3
  end
@@ -0,0 +1,5 @@
1
+ class Path < ActiveRecord::Base
2
+ self.table_name = 'traffic.paths'
3
+
4
+ lookup_by :path, cache: 40, find_or_create: true
5
+ end
@@ -1,5 +1,3 @@
1
1
  class PostalCode < ActiveRecord::Base
2
- attr_accessible :postal_code
3
-
4
2
  lookup_by :postal_code, cache: 2
5
3
  end
@@ -1,5 +1,3 @@
1
1
  class State < ActiveRecord::Base
2
- attr_accessible :state
3
-
4
2
  lookup_by :state, cache: true
5
3
  end
@@ -1,6 +1,4 @@
1
1
  class Status < ActiveRecord::Base
2
- attr_accessible :status
3
-
4
2
  lookup_by :status, normalize: true
5
3
 
6
4
  def status=(arg)
@@ -1,5 +1,3 @@
1
1
  class Street < ActiveRecord::Base
2
- attr_accessible :street
3
-
4
2
  lookup_by :street, find_or_create: true
5
3
  end
@@ -0,0 +1,3 @@
1
+ class UserAgent < ActiveRecord::Base
2
+ lookup_by :user_agent, cache: 2, find_or_create: true
3
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -1,20 +1,28 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- require "active_record/railtie"
3
+ require "rails/all"
4
+
5
+ # Require the gems listed in Gemfile, including any gems
6
+ # you've limited to :test, :development, or :production.
7
+ Bundler.require(:default, Rails.env)
4
8
 
5
- Bundler.require
6
9
  require "lookup_by"
7
10
 
8
11
  module Dummy
9
12
  class Application < Rails::Application
10
- # Configure the default encoding used in templates for Ruby 1.9.
11
- config.encoding = "utf-8"
12
-
13
- # Enforce whitelist mode for mass assignment.
14
- # This will create an empty whitelist of attributes available for mass-assignment for all models
15
- # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
16
- # parameters by using an attr_accessible or attr_protected declaration.
17
- config.active_record.whitelist_attributes = true
13
+ # Settings in config/environments/* take precedence over those specified here.
14
+ # Application configuration should go into files in config/initializers
15
+ # -- all .rb files in that directory are automatically loaded.
16
+
17
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
18
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
19
+ # config.time_zone = 'Central Time (US & Canada)'
20
+
21
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
22
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
23
+ # config.i18n.default_locale = :de
24
+
25
+ config.active_record.schema_format = :sql
18
26
  end
19
27
  end
20
28
 
@@ -1,10 +1,6 @@
1
- require 'rubygems'
2
- gemfile = File.expand_path('../../../../Gemfile', __FILE__)
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
3
 
4
- if File.exist?(gemfile)
5
- ENV['BUNDLE_GEMFILE'] = gemfile
6
- require 'bundler'
7
- Bundler.setup
8
- end
4
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
9
5
 
10
- $:.unshift File.expand_path('../../../../lib', __FILE__)
6
+ $:.unshift File.expand_path('../../../../lib', __FILE__)
@@ -1,5 +1,5 @@
1
- # Load the rails application
1
+ # Load the Rails application.
2
2
  require File.expand_path('../application', __FILE__)
3
3
 
4
- # Initialize the rails application
4
+ # Initialize the Rails application.
5
5
  Dummy::Application.initialize!
@@ -1,15 +1,29 @@
1
1
  Dummy::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
2
4
  # In the development environment your application's code is reloaded on
3
5
  # every request. This slows down response time but is perfect for development
4
6
  # since you don't have to restart the web server when you make code changes.
5
7
  config.cache_classes = false
6
8
 
7
- # Log error messages when you accidentally call methods on nil.
8
- config.whiny_nils = true
9
+ # Do not eager load code on boot.
10
+ config.eager_load = false
11
+
12
+ # Show full error reports and disable caching.
13
+ config.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
9
15
 
10
- # Print deprecation notices to the Rails logger
16
+ # Don't care if the mailer can't send.
17
+ config.action_mailer.raise_delivery_errors = false
18
+
19
+ # Print deprecation notices to the Rails logger.
11
20
  config.active_support.deprecation = :log
12
21
 
13
- # Raise exception on mass assignment protection for Active Record models
14
- config.active_record.mass_assignment_sanitizer = :strict
22
+ # Raise an error on page load if there are pending migrations
23
+ config.active_record.migration_error = :page_load
24
+
25
+ # Debug mode disables concatenation and preprocessing of assets.
26
+ # This option may cause significant delays in view rendering with a large
27
+ # number of complex assets.
28
+ config.assets.debug = true
15
29
  end