offline_lookup 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81bf5d7f3480aad4023793d4b1080942f3e61062
4
- data.tar.gz: 120e4eb224c5533fa99d2d582e7aa24f42959fc3
3
+ metadata.gz: a980e6edb149fd22cc7c81f2986ddac4e6ca27dc
4
+ data.tar.gz: dedadea949a7706a88188eb2bd0a03b31530ac34
5
5
  SHA512:
6
- metadata.gz: 7a309b3d4386cd90102fa0db083fca4a727b6f2fdf6deb7f78db82398f6c0a522ffdcf00d3135a63b0d8c7976d6aaa46a724c31dd5f7889fa7fb40aa559ee9c2
7
- data.tar.gz: e57b182948563dff3bd076bbc59ea488aaccd59d9ec60ef4b542fc86deba26a0541ccd89b667a81c1cefe57fbee8064e3c6c85795776e4d430663ea12ad26e2d
6
+ metadata.gz: 1bd3263c95eb71ea8a2a5f511ee075a8a893eaf56f9aedf5c40b84c5addd49ad1230e2506270f407010c719391602e713e0ec8ea641585f3af134df3ecb63a9d
7
+ data.tar.gz: cdefda2de296628c482b77bd156b08213a1205d696674682524ba6e99cc3ceb1be595d50e1d280ad6fae148146e76ed7f91b1e20c6b2a80511454aadace64ac4
data/README.md CHANGED
@@ -1,18 +1,71 @@
1
- ##Offline Lookup
1
+ #Offline Lookup
2
2
 
3
- Alhpa (0.0.3). Use at your own risk.
3
+ Store lookup values for small tables for fewer db queries and syntactic sugar.
4
4
 
5
- IMPORTANT: don't use this for models that have a lot of data!!
6
- This is basically a in-memory pseudo-index intended to speed up quick, repeated
7
- finds of index table values without trips to an external db machine.
8
- It's also nicer syntax, e.g:
5
+ ## The quickest examples:
6
+ Lets say you have a model `PostType` with a few rows that define various types of Posts.
7
+
8
+ `PostType.find_by(name: "reply").id`
9
+ becomes simply
10
+ `Post.reply_id`
11
+
12
+ Where the latter form does not execute a database query. Instead, a mapping of id to name is kept in the class instance. Thus, this is good only for small lookup-type tables, not for full tables of data.
13
+
14
+ A couple more:
15
+ `PostType.find_by(name: name)` becomes `PostType.lookup(name)`
16
+ `post_type.name == "reply"` becomes `post_type.reply?` (disabled by default, use `identity_methods: true`)
17
+ `Post.find_by(name: "reply") becomes `PostType.reply` (disabled by default, use `lookup_methods: true`)
18
+
19
+ More flexible, still no db queries:
20
+ `PostType.find_by(name: name).id` becomes `PostType.id_for_name(name)`
21
+ `PostType.find(1).name` becomes `PostType.name_for_id(1)`
22
+
23
+ IMPORTANT: don't use this for models that have a lot of data!! While OfflineLookup only stores primary key and lookup column values, you don't usually want this loading thousands or millions of such values into memory. Rule of thumb, keep it in the tens or below.
24
+
25
+ ### What's New
26
+
27
+ v1.0.0
28
+ `lookup_methods` and `identity_methods` now default to false. If you use the `TurnaroundLevel.two_hour` or `TurnaroundLevel.first.two_hour?` methods, pass either `lookup_methods: true` or `identity_methods: true` into `use_offline_lookup`.
29
+
30
+ Add `lookup` method to allow lookup by key'd name without risking bad / reserved-keyword method names (e.g. "parent")
31
+
32
+ You can now specify multiple columns for lookup! The values are by default joined with a " " (note this translates to "_" for method names). You can configure this delimiter and what to do with `nil` values.
33
+
34
+
35
+
36
+ ## How To Use It
37
+
38
+ ### By Example
9
39
 
10
40
  ```
41
+ class TurnaroundLevel
42
+ use_offline_lookup :name
43
+ end
44
+
45
+ # Return the id of the TurnaroundLevel named "Two Hour"
11
46
  TurnaroundLevel.two_hour_id
12
- TurnaroundLevel.quick_lookup("Two Hour")
13
- Service.last.turnaround_level.two_hour?
47
+ TurnaroundLevel.id_for_name("Two Hour")
48
+ # Return the instance of TurnaroundLevel with the name "Two Hour"
49
+ TurnaroundLevel.lookup("Two Hour")
50
+ #Return the name of the TurnaroundLevel with id 7
51
+ TurnaroundLevel.name_for_id(7)
52
+ ```
53
+
54
+ A few extra options:
55
+ ```
56
+ class TurnaroundLevel
57
+ use_offline_lookup :name, lookup_method: true, identity_method: true
58
+ end
59
+
60
+ # Return true if the last TurnaroundLevel is the "Two Hour" level. Uses the `:identify_methods` options.
61
+ TurnaroundLevel.last.two_hour?
62
+ # Return the "Two Hour" TurnaroundLevel instance. Uses the `:lookup_methods` option
63
+ TurnaroundLevel.two_hour
64
+
14
65
  ```
15
66
 
67
+ ### By Spec
68
+
16
69
  In any ActiveRecord::Base subclass, use:
17
70
 
18
71
  `use_offline_id_lookup column_name`
@@ -40,22 +93,53 @@ TurnaroundLevel.name_for_id(1)
40
93
  #=> 'Same Day'
41
94
  TurnaroundLevel.id_for_name('Same Day')
42
95
  #=> 1
96
+ TurnaroundLevel.lookup(`Same Day`)
97
+ #=> <#TurnaroundLevel id: 1, level: "Same Day", ...>
98
+ ```
99
+
100
+ If you use the option `identity_methods: true`, you get
101
+
102
+ ```
43
103
  TurnaroundLevel.first.same_day?
44
104
  #=> true
105
+ ```
106
+
107
+ Using `lookup_methods: true`:
108
+
109
+ ```
45
110
  TurnaroundLevel.same_day
46
111
  #=> <#TurnaroundLevel id: 1, level: "Same Day", ...>
47
112
  ```
48
113
 
49
- The last of these methods is the "lookup" method, and is not quite offline. This is because we only store the key - name mappings, not the entire objects, in memory when we declare a new offline_lookup model. However it is included by default for convenient syntax (and it uses a lookup on what is usually the primary key of the table, in case the extra few ms matter to you). You can disable it by using
114
+ This is not quite offline. This is because we only store the key - name mappings, not the entire objects, in memory when we declare a new offline_lookup model. However it is included by default for convenient syntax (and it uses a lookup on what is usually the primary key of the table, in case the extra few ms matter to you).
115
+
116
+
117
+ You can use combinations of columns to define the lookup values
50
118
 
51
- `use_offline_lookup :level, lookup_methods: false`
119
+ ```
120
+ class Admin < ActiveRecord::Base # firstname, lastname
121
+ use_offline_lookup :firstname, :lastname
122
+ end
52
123
 
53
- You can also disable the identity methods (e.g. `TurnaroundLevel.first.same_day?`) by using
124
+ Admin.john_doe_id
125
+ Admin.lookup("John Doe")
126
+ ```
54
127
 
55
- `use_offline_lookup :level, identity_methods: false`
128
+ Option include
129
+ `delimiter` (default: `'_'`): character or string to join values between columns
130
+ `compact` (default: `false`): exclude nil columns from joining with delimiter
131
+ `name` (default: fields.join(delimiter)): name for this lookup
56
132
 
57
- (And yes, the keywords can be combined, they're all optional keyword args)
133
+ E.g. on `name`:
134
+ ```
135
+ class Admin < ActiveRecord::Base # firstname, lastname
136
+ use_offline_lookup :firstname, :lastname, name: "name"
137
+ end
138
+ Admin.id_for_name("John Doe")
139
+ ```
58
140
 
59
141
  ## Known Issues
60
142
 
61
- If two entries in the table have the same value in the specified field, all but one will get overwritten. In a future version, I plan to allow multiple-column specificaion, e.g. `use_offline_lookup [:firstname, :lastname]`
143
+ If two entries in the table have the same value in the specified field, all but one will get overwritten.
144
+
145
+ Be aware that if the lookup name is a keyword or existing method, this can cause issues! For example, I encountered a use of offline lookup where the lookup column was `lastname` of a small `Admin` table. One of the last names was "parent". With `identity_methods: true`, this defined the `Admin.parent` method, which of course caused issues since `parent` on a class is supposed to mean something else! If you have potentially dangerous values, leave lookup_methods disabled and just use `Admin.lookup("Parent")` instead.
@@ -1,114 +1,8 @@
1
- # TODO: provide multiple column names in a single call (e.g. firstname, lastname)
2
1
  # TODO: support multiple offline lookups per model
3
2
  # TODO: support scope arg in use_offline_lookup (partial index)
4
3
 
5
- module OfflineLookup
6
- module ActiveRecord
7
- def use_offline_lookup(field = :name, key: :id, identity_methods: true, lookup_methods: true)
8
- class_attribute :offline_lookup_values, :offline_lookup_options
9
- self.offline_lookup_options = {
10
- field: field.to_s,
11
- key: key.to_s,
12
- identity_methods: !!identity_methods,
13
- lookup_methods: !!lookup_methods
14
- }.freeze
15
- self.offline_lookup_values = self.all.pluck(key, field).to_h.freeze
4
+ require 'offline_lookup/active_record.rb'
5
+ require 'offline_lookup/base.rb'
6
+ require 'offline_lookup/builder.rb'
16
7
 
17
- include OfflineLookup::Base
18
- end
19
- end
20
-
21
- class Builder
22
- def initialize(options)
23
- @field = options[:field]
24
- @key = options[:key]
25
- end
26
-
27
- def sanitize(string)
28
- #:methodize went away. Where did it go?
29
- #1. Replace illegal chars and _ boundaries with " " boundary
30
- string = string.gsub(/[^a-zA-Z\d]+/," ").strip
31
- #2. Insert " " boundary at snake-case boundaries
32
- string.gsub!(/([a-z])([A-Z])/){|s| "#{$1} #{$2}"}
33
- #3. underscore
34
- string.gsub!(/\s+/, "_")
35
- string.downcase!
36
- #4. Append underscore if name begins with digit
37
- string = "_#{string}" if string.length == 0 || string[0] =~ /\d/
38
- return string
39
- end
40
-
41
- # e.g., :two_hour_id
42
- def key_method_name(value)
43
- sanitize "#{value}_#{@key}"
44
- end
45
-
46
- def lookup_method_name(value)
47
- sanitize value.to_s
48
- end
49
-
50
- # e.g., :two_hour?
51
- def indentiy_method_name(value)
52
- lookup_method_name(value) + "?"
53
- end
54
-
55
- # e.g. :name_for_id(id)
56
- def field_for_key_method_name
57
- sanitize "#{@field}_for_#{@key}"
58
- end
59
-
60
- # e.g. :id_for_name(name)
61
- def key_for_field_method_name
62
- sanitize "#{@key}_for_#{@field}"
63
- end
64
-
65
- end
66
-
67
- module Base
68
- extend ActiveSupport::Concern
69
-
70
- included do
71
- builder = OfflineLookup::Builder.new(self.offline_lookup_options)
72
-
73
- ### define value-named methods such as :two_hour_id and :two_hour?
74
-
75
- self.offline_lookup_values.each do |key, value|
76
- # class method: get key value (e.g. FooType.bar_id)
77
- define_singleton_method(builder.key_method_name(value)) do
78
- key
79
- end
80
-
81
- # instance method: true if instance is of named type (e.g. FooType.first.bar?)
82
- if self.offline_lookup_options[:identity_methods]
83
- define_method(builder.indentiy_method_name(value)) do
84
- self.attributes[self.offline_lookup_options[:key]] == key
85
- end
86
- end
87
-
88
- # class method: get instance by named method (e.g. FooType.bar)
89
- # not "Offline", but lookup by indexed key. Also, synactic sugar.
90
- if self.offline_lookup_options[:lookup_methods]
91
- define_singleton_method(builder.lookup_method_name(value)) do
92
- key = self.offline_lookup_values.find{|k, v| v.to_s == value.to_s}.first
93
- find(key)
94
- end
95
- end
96
- end
97
-
98
-
99
- ### define statically-named methods where you pass in the named value, e.g., id_for_name(:two_hour)
100
- # e.g. FooType.name_for_id(1)
101
- define_singleton_method(builder.field_for_key_method_name) do |key_value|
102
- self.offline_lookup_values[key_value]
103
- end
104
-
105
- # e.g. FooType.id_for_name("Bar")
106
- define_singleton_method(builder.key_for_field_method_name) do |field_value|
107
- self.offline_lookup_values.find{|k, v| v.to_s == field_value.to_s}.first
108
- end
109
-
110
- end
111
- end
112
- end
113
-
114
- ActiveRecord::Base.extend OfflineLookup::ActiveRecord
8
+ ActiveRecord::Base.include OfflineLookup::ActiveRecord
@@ -0,0 +1,42 @@
1
+ module OfflineLookup
2
+ module ActiveRecord
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_create :get_offline_lookup_values
7
+ after_destroy :get_offline_lookup_values
8
+ end
9
+
10
+ def get_offline_lookup_values
11
+ self.class.get_offline_lookup_values
12
+ end
13
+
14
+ module ClassMethods
15
+ def use_offline_lookup(*fields, key: "id", identity_methods: false, lookup_methods: false, compact: false, delimiter: " ", name: fields.join(delimiter), transform: nil)
16
+ class_attribute :offline_lookup_values, :offline_lookup_options
17
+ self.offline_lookup_options = {
18
+ fields: fields.map(&:to_s),
19
+ key: key.to_s,
20
+ identity_methods: !!identity_methods,
21
+ lookup_methods: !!lookup_methods,
22
+ compact: !!compact,
23
+ delimiter: delimiter.to_s,
24
+ name: name,
25
+ transform: transform
26
+ }.freeze
27
+
28
+ get_offline_lookup_values
29
+
30
+ include OfflineLookup::Base
31
+ end
32
+
33
+ def get_offline_lookup_values
34
+ self.offline_lookup_values = self.all.pluck(offline_lookup_options[:key], *offline_lookup_options[:fields]).map do |key, *fields|
35
+ fields.compact! if offline_lookup_options[:compact]
36
+ value = offline_lookup_options[:transform].present? ? offline_lookup_options[:transform].call(*fields.map(&:to_s)) : fields.map(&:to_s).join(offline_lookup_options[:delimiter])
37
+ [key, value]
38
+ end.to_h.freeze
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ module OfflineLookup
2
+ module Base
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ builder = OfflineLookup::Builder.new(self.offline_lookup_options)
7
+
8
+ ### define value-named methods such as :two_hour_id and :two_hour?
9
+
10
+ self.offline_lookup_values.each do |key, value|
11
+ # class method: get key value (e.g. FooType.bar_id)
12
+ define_singleton_method(builder.key_method_name(value)) do
13
+ key
14
+ end
15
+
16
+ # instance method: true if instance is of named type (e.g. FooType.first.bar?)
17
+ if self.offline_lookup_options[:identity_methods]
18
+ define_method(builder.indentiy_method_name(value)) do
19
+ self.attributes[self.offline_lookup_options[:key]] == key
20
+ end
21
+ end
22
+
23
+ # class method: get instance by named method (e.g. FooType.bar)
24
+ # not "Offline", but lookup by indexed key. Also, synactic sugar.
25
+ if self.offline_lookup_options[:lookup_methods]
26
+ define_singleton_method(builder.lookup_method_name(value)) do
27
+ key = self.offline_lookup_values.find{|k, v| v.to_s == value.to_s}.try(:first)
28
+ find(key)
29
+ end
30
+ end
31
+
32
+ # class method: get instance using more general `lookup` method
33
+ # Just as not "offline" as above, but less dangerous / more robust to any db value
34
+ define_singleton_method :lookup do |value|
35
+ key = self.offline_lookup_values.find{|k, v| v.to_s == value.to_s}.try(:first)
36
+ find_by(id: key)
37
+ end
38
+ end
39
+
40
+
41
+ ### define statically-named methods where you pass in the named value, e.g., id_for_name(:two_hour)
42
+ # e.g. FooType.name_for_id(1)
43
+ define_singleton_method(builder.field_for_key_method_name) do |key_value|
44
+ self.offline_lookup_values[key_value]
45
+ end
46
+
47
+ # e.g. FooType.id_for_name("Bar")
48
+ define_singleton_method(builder.key_for_field_method_name) do |field_value|
49
+ self.offline_lookup_values.find{|k, v| v.to_s == field_value.to_s}.try(:first)
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,48 @@
1
+ module OfflineLookup
2
+ class Builder
3
+ def initialize(options)
4
+ @fields = options[:fields]
5
+ @key = options[:key]
6
+ @name = options[:name]
7
+ end
8
+
9
+ def sanitize(string)
10
+ #:methodize went away. Where did it go?
11
+ #1. Replace illegal chars and _ boundaries with " " boundary
12
+ string = string.gsub(/[^a-zA-Z\d]+/," ").strip
13
+ #2. Insert " " boundary at snake-case boundaries
14
+ string.gsub!(/([a-z])([A-Z])/){|s| "#{$1} #{$2}"}
15
+ #3. underscore
16
+ string.gsub!(/\s+/, "_")
17
+ string.downcase!
18
+ #4. Append underscore if name begins with digit
19
+ string = "_#{string}" if string.length == 0 || string[0] =~ /\d/
20
+ return string
21
+ end
22
+
23
+ # e.g., :two_hour_id
24
+ def key_method_name(value)
25
+ sanitize "#{value}_#{@key}"
26
+ end
27
+
28
+ def lookup_method_name(value)
29
+ sanitize value.to_s
30
+ end
31
+
32
+ # e.g., :two_hour?
33
+ def indentiy_method_name(value)
34
+ lookup_method_name(value) + "?"
35
+ end
36
+
37
+ # e.g. :name_for_id(id)
38
+ def field_for_key_method_name
39
+ sanitize "#{@name}_for_#{@key}"
40
+ end
41
+
42
+ # e.g. :id_for_name(name)
43
+ def key_for_field_method_name
44
+ sanitize "#{@key}_for_#{@name}"
45
+ end
46
+
47
+ end
48
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: offline_lookup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Schwartz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-21 00:00:00.000000000 Z
11
+ date: 2016-09-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Offline indexing of small tables for speed & convenience
13
+ description: Offline indexing of small tables syntactic sugar & less db touching
14
14
  email: ozydingo@gmail.com
15
15
  executables: []
16
16
  extensions: []
@@ -18,6 +18,9 @@ extra_rdoc_files: []
18
18
  files:
19
19
  - README.md
20
20
  - lib/offline_lookup.rb
21
+ - lib/offline_lookup/active_record.rb
22
+ - lib/offline_lookup/base.rb
23
+ - lib/offline_lookup/builder.rb
21
24
  homepage:
22
25
  licenses:
23
26
  - MIT