lookup_by 0.2.0 → 0.3.1

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