birddog 0.0.8 → 0.0.9.p1
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.
- data/birddog.gemspec +0 -1
- data/lib/birddog/birddog.rb +46 -34
- data/lib/birddog/field_conditions.rb +53 -33
- data/lib/birddog/scope_builder.rb +26 -0
- data/lib/birddog/version.rb +1 -1
- data/lib/birddog.rb +0 -5
- data/specs/birddog_spec.rb +0 -5
- data/specs/spec_helper.rb +21 -28
- metadata +21 -34
data/birddog.gemspec
CHANGED
data/lib/birddog/birddog.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'birddog/scope_builder'
|
2
|
+
|
1
3
|
module Birddog
|
2
4
|
|
3
5
|
def self.included(base)
|
@@ -23,7 +25,7 @@ module Birddog
|
|
23
25
|
|
24
26
|
def field(name, options={}, &mapping)
|
25
27
|
@fields[name] = field = {
|
26
|
-
:attribute => options.fetch(:attribute,
|
28
|
+
:attribute => field_attribute(name, options.fetch(:attribute, nil), options.fetch(:aggregate, nil)),
|
27
29
|
:cast => options.fetch(:cast, false),
|
28
30
|
:type => options.fetch(:type, :string),
|
29
31
|
:case_sensitive => options.fetch(:case_sensitive, true),
|
@@ -47,27 +49,36 @@ module Birddog
|
|
47
49
|
aggregate?(name) ? define_aggregate_field(name, @fields[name]) : define_field(name, @fields[name])
|
48
50
|
end
|
49
51
|
|
52
|
+
def aggregatable(*fields)
|
53
|
+
fields.flatten.compact.uniq.each do |agg_field|
|
54
|
+
averagable(agg_field)
|
55
|
+
minimumable(agg_field)
|
56
|
+
maximumable(agg_field)
|
57
|
+
sumable(agg_field)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
50
61
|
def averagable(*fields)
|
51
|
-
fields.flatten.each do |avg_field|
|
52
|
-
@averagable << avg_field
|
62
|
+
fields.flatten.compact.uniq.each do |avg_field|
|
63
|
+
@averagable << avg_field unless @averagable.include?(avg_field)
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
56
67
|
def minimumable(*fields)
|
57
|
-
fields.flatten.each do |min_field|
|
58
|
-
@minimumable << min_field
|
68
|
+
fields.flatten.compact.uniq.each do |min_field|
|
69
|
+
@minimumable << min_field unless @minimumable.include?(min_field)
|
59
70
|
end
|
60
71
|
end
|
61
72
|
|
62
73
|
def maximumable(*fields)
|
63
|
-
fields.flatten.each do |max_field|
|
64
|
-
@maximumable << max_field
|
74
|
+
fields.flatten.compact.uniq.each do |max_field|
|
75
|
+
@maximumable << max_field unless @maximumable.include?(max_field)
|
65
76
|
end
|
66
77
|
end
|
67
78
|
|
68
79
|
def sumable(*fields)
|
69
|
-
fields.flatten.each do |sum_field|
|
70
|
-
@sumable << sum_field
|
80
|
+
fields.flatten.compact.uniq.each do |sum_field|
|
81
|
+
@sumable << sum_field unless @sumable.include?(sum_field)
|
71
82
|
end
|
72
83
|
end
|
73
84
|
|
@@ -76,23 +87,26 @@ module Birddog
|
|
76
87
|
end
|
77
88
|
|
78
89
|
def search(query)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
scope_for(model, key, value)
|
83
|
-
end
|
90
|
+
key, value = tokenize(query)
|
91
|
+
key, value = "text_search", key if value.nil?
|
92
|
+
scope_for(@model, key, value)
|
84
93
|
end
|
85
94
|
|
86
|
-
|
87
|
-
options = fields.extract_options!
|
88
|
-
fields = fields.map { |f| "LOWER(#{f}) LIKE :value" }.join(" OR ")
|
95
|
+
################## PRIVATES ####################
|
89
96
|
|
90
|
-
|
91
|
-
|
92
|
-
end
|
97
|
+
def aggregate_and_aliased?(aggregate)
|
98
|
+
aggregate && aggregate.respond_to?(:alias) && aggregate.alias
|
93
99
|
end
|
94
|
-
|
95
|
-
|
100
|
+
private :aggregate_and_aliased?
|
101
|
+
|
102
|
+
def field_attribute(name, val, aggregate)
|
103
|
+
val = name.to_sym unless val
|
104
|
+
val = ::Arel::Nodes::SqlLiteral.new(aggregate.alias) if aggregate_and_aliased?(aggregate)
|
105
|
+
val = @model.arel_table[val] if val.is_a?(Symbol)
|
106
|
+
val = ::Arel::Nodes::SqlLiteral.new(val) if val.is_a?(String)
|
107
|
+
return val
|
108
|
+
end
|
109
|
+
private :field_attribute
|
96
110
|
|
97
111
|
def conditional?(value)
|
98
112
|
value.index(/[<>=]/) != nil
|
@@ -103,18 +117,16 @@ module Birddog
|
|
103
117
|
field[:options].merge!(:select => field[:aggregate])
|
104
118
|
|
105
119
|
define_scope(name) do |value|
|
106
|
-
|
107
|
-
|
108
|
-
else
|
109
|
-
field[:options]
|
110
|
-
end
|
120
|
+
current_scope = ::Birddog::ScopeBuilder.build(@model, field[:options])
|
121
|
+
conditional?(value) ? setup_conditions(current_scope, field, value) : current_scope
|
111
122
|
end
|
112
123
|
end
|
113
124
|
private :define_aggregate_field
|
114
125
|
|
115
126
|
def define_field(name, field)
|
116
127
|
define_scope(name) do |value|
|
117
|
-
field[:options]
|
128
|
+
current_scope = ::Birddog::ScopeBuilder.build(@model, field[:options])
|
129
|
+
setup_conditions(current_scope, field, value)
|
118
130
|
end
|
119
131
|
end
|
120
132
|
private :define_field
|
@@ -169,20 +181,20 @@ module Birddog
|
|
169
181
|
end
|
170
182
|
private :parse_condition
|
171
183
|
|
172
|
-
def setup_conditions(field, value)
|
184
|
+
def setup_conditions(current_scope, field, value)
|
173
185
|
condition = parse_condition(value)
|
174
186
|
value = callable_or_cast(field, condition, value)
|
175
187
|
value = field[:mapping].call(value)
|
176
188
|
|
177
189
|
case field[:type]
|
178
190
|
when :string then
|
179
|
-
conditions_for_string_search(field, value)
|
191
|
+
conditions_for_string_search(current_scope, field, condition, value)
|
180
192
|
when :float, :decimal, :integer then
|
181
|
-
conditions_for_numeric(field, condition, value)
|
193
|
+
conditions_for_numeric(current_scope, field, condition, value, field[:type])
|
182
194
|
when :date, :datetime, :time then
|
183
|
-
conditions_for_date(field, condition, value)
|
195
|
+
conditions_for_date(current_scope, field, condition, value)
|
184
196
|
else
|
185
|
-
|
197
|
+
current_scope.where(field[:attribute] => value)
|
186
198
|
end
|
187
199
|
end
|
188
200
|
private :setup_conditions
|
@@ -191,7 +203,7 @@ module Birddog
|
|
191
203
|
split_tokens = query.split(":")
|
192
204
|
split_tokens.each { |tok| tok.strip! }
|
193
205
|
|
194
|
-
[
|
206
|
+
[split_tokens.shift, split_tokens.join(":")]
|
195
207
|
end
|
196
208
|
private :tokenize
|
197
209
|
|
@@ -2,51 +2,71 @@ module Birddog
|
|
2
2
|
|
3
3
|
module FieldConditions
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
}
|
10
|
-
|
11
|
-
def conditions_for_string_search(field, value)
|
12
|
-
search_con = " LIKE ? "
|
13
|
-
field_to_search = "lower(#{field[:attribute]})"
|
14
|
-
value_to_search = value.downcase
|
15
|
-
|
16
|
-
if field[:case_sensitive]
|
17
|
-
field_to_search = field[:attribute]
|
18
|
-
value_to_search = value
|
19
|
-
end
|
5
|
+
def conditions_for_string_search(current_scope, field, condition, value)
|
6
|
+
search_con = condition
|
7
|
+
strict_equality = (search_con == "==")
|
8
|
+
value_to_search = (value ? value.dup : nil)
|
20
9
|
|
21
|
-
if
|
22
|
-
|
23
|
-
|
10
|
+
if !strict_equality
|
11
|
+
if field[:match_substring]
|
12
|
+
search_con = "=~"
|
13
|
+
value_to_search = "%#{value_to_search}%"
|
14
|
+
end
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
if field[:regex] && regexed?(value)
|
17
|
+
# TODO check db driver to determine regex operator for DB (current is Postgres)
|
18
|
+
search_con = "=~"
|
19
|
+
value_to_search = value[1..value.size-2]
|
20
|
+
elsif field[:wildcard] && value_to_search.include?("*")
|
21
|
+
search_con = "=~"
|
22
|
+
value_to_search.gsub!(/[\*]/, "%")
|
23
|
+
end
|
32
24
|
end
|
33
25
|
|
34
|
-
[
|
26
|
+
conditionally_scoped(current_scope, field[:attribute], search_con, value_to_search, field[:aggregate])
|
27
|
+
end
|
28
|
+
|
29
|
+
def conditions_for_date(current_scope, field, condition, value)
|
30
|
+
conditionally_scoped(current_scope, field[:attribute], condition, value.value.strftime("%Y-%m-%d"), field[:aggregate])
|
35
31
|
end
|
36
32
|
|
37
|
-
def
|
38
|
-
|
33
|
+
def conditions_for_numeric(current_scope, field, condition, value, field_type)
|
34
|
+
conditionally_scoped(current_scope, field[:attribute], condition, cast_numeric(field_type, value), field[:aggregate], true)
|
39
35
|
end
|
40
36
|
|
41
|
-
def
|
42
|
-
|
37
|
+
def cast_numeric(field_type, value)
|
38
|
+
case field_type
|
39
|
+
when :integer then
|
40
|
+
value.to_i
|
41
|
+
else
|
42
|
+
value.to_f
|
43
|
+
end
|
44
|
+
end
|
43
45
|
|
44
|
-
|
46
|
+
def conditionally_scoped(current_scope, field, condition, value, aggregate, is_numeric = false)
|
47
|
+
having_or_where = (aggregate ? :having : :where)
|
48
|
+
scope = case condition
|
45
49
|
when "=~", "~=" then
|
46
|
-
|
50
|
+
if is_numeric
|
51
|
+
current_scope.__send__(having_or_where, field.gteq(value.floor).and(field.lt((value + 1).floor)))
|
52
|
+
else
|
53
|
+
current_scope.__send__(having_or_where, field.matches(value))
|
54
|
+
end
|
55
|
+
when :<, "<" then
|
56
|
+
current_scope.__send__(having_or_where, field.lt(value))
|
57
|
+
when :>, ">" then
|
58
|
+
current_scope.__send__(having_or_where, field.gt(value))
|
59
|
+
when :<=, "<=", "=<" then
|
60
|
+
current_scope.__send__(having_or_where, field.lteq(value))
|
61
|
+
when :>=, ">=", "=>" then
|
62
|
+
current_scope.__send__(having_or_where, field.gteq(value))
|
63
|
+
when "=", "==" then
|
64
|
+
current_scope.__send__(having_or_where, field.eq(value))
|
47
65
|
else
|
48
|
-
|
66
|
+
raise "#{condition} not defined for #{field}"
|
49
67
|
end
|
68
|
+
|
69
|
+
return scope
|
50
70
|
end
|
51
71
|
|
52
72
|
def regexed?(value)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Birddog
|
2
|
+
|
3
|
+
class ScopeBuilder
|
4
|
+
|
5
|
+
def self.build(model, scope_options)
|
6
|
+
new(model, scope_options).build
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(model, scope_options)
|
10
|
+
@options = scope_options
|
11
|
+
@model = model
|
12
|
+
end
|
13
|
+
|
14
|
+
def build
|
15
|
+
scope = @model.scoped
|
16
|
+
|
17
|
+
@options.each do |k, v|
|
18
|
+
scope = scope.__send__(k, v)
|
19
|
+
end
|
20
|
+
|
21
|
+
return scope
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/birddog/version.rb
CHANGED
data/lib/birddog.rb
CHANGED
@@ -17,11 +17,6 @@
|
|
17
17
|
require 'chronic'
|
18
18
|
require 'active_support/core_ext'
|
19
19
|
require 'active_record'
|
20
|
-
require 'squeel'
|
21
|
-
|
22
|
-
Squeel.configure do |config|
|
23
|
-
config.load_core_extensions :hash
|
24
|
-
end
|
25
20
|
|
26
21
|
require "birddog/version"
|
27
22
|
require "birddog/field_conditions"
|
data/specs/birddog_spec.rb
CHANGED
@@ -36,11 +36,6 @@ describe Birddog::Birddog do ####################
|
|
36
36
|
User.scopes_for_query("last_name:Doe").size.must_equal(1)
|
37
37
|
end
|
38
38
|
|
39
|
-
it "can find using case insensitive search" do
|
40
|
-
User.scopes_for_query("insensitive_last_name:doe").must_include(@john)
|
41
|
-
User.scopes_for_query("insensitive_last_name:doe").size.must_equal(1)
|
42
|
-
end
|
43
|
-
|
44
39
|
it "can find matching substrings" do
|
45
40
|
User.scopes_for_query("substringed_last_name:oe").must_include(@john)
|
46
41
|
User.scopes_for_query("substringed_last_name:oe").size.must_equal(1)
|
data/specs/spec_helper.rb
CHANGED
@@ -30,6 +30,24 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
class Product < ActiveRecord::Base
|
34
|
+
include Birddog
|
35
|
+
|
36
|
+
belongs_to :user
|
37
|
+
|
38
|
+
birddog do |search|
|
39
|
+
search.aggregatable :value
|
40
|
+
search.field :name, :regex => true, :wildcard => true
|
41
|
+
search.field :cast_val, :type => :decimal, :cast => lambda { |v| v }, :attribute => :value
|
42
|
+
search.field :value, :type => :decimal
|
43
|
+
search.field :available, :type => :boolean
|
44
|
+
|
45
|
+
search.keyword :sort do |value|
|
46
|
+
order(search.fields[value.to_sym][:attribute].desc)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
33
51
|
class User < ActiveRecord::Base
|
34
52
|
include Birddog
|
35
53
|
|
@@ -39,9 +57,9 @@ class User < ActiveRecord::Base
|
|
39
57
|
search.field :first_name
|
40
58
|
search.alias_field :name, :first_name
|
41
59
|
search.field :last_name
|
42
|
-
search.field :total_product_value, :type => :decimal, :aggregate =>
|
43
|
-
search.field :insensitive_last_name, :attribute =>
|
44
|
-
search.field :substringed_last_name, :attribute =>
|
60
|
+
search.field :total_product_value, :type => :decimal, :aggregate => Product.arel_table[:value].sum.as("total_product_value")
|
61
|
+
search.field :insensitive_last_name, :attribute => arel_table[:last_name], :case_sensitive => false
|
62
|
+
search.field :substringed_last_name, :attribute => arel_table[:last_name], :match_substring => true
|
45
63
|
search.field :available_product, :type => :boolean,
|
46
64
|
:attribute => "products.available",
|
47
65
|
:joins => :products
|
@@ -51,30 +69,5 @@ class User < ActiveRecord::Base
|
|
51
69
|
joins("LEFT OUTER JOIN products AS products ON (products.user_id = users.id)").
|
52
70
|
group(arel_table[:id])
|
53
71
|
end
|
54
|
-
|
55
|
-
search.text_search "first_name", "last_name", "products.name", :include => :products
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class Product < ActiveRecord::Base
|
60
|
-
include Birddog
|
61
|
-
|
62
|
-
belongs_to :user
|
63
|
-
|
64
|
-
birddog do |search|
|
65
|
-
search.averagable :value
|
66
|
-
search.minimumable :value
|
67
|
-
search.maximumable :value
|
68
|
-
search.sumable :value
|
69
|
-
search.text_search "products.name", "products.value"
|
70
|
-
|
71
|
-
search.field :name, :regex => true, :wildcard => true
|
72
|
-
search.field :cast_val, :type => :decimal, :cast => lambda { |v| v }, :attribute => :value
|
73
|
-
search.field :value, :type => :decimal
|
74
|
-
search.field :available, :type => :boolean
|
75
|
-
|
76
|
-
search.keyword :sort do |value|
|
77
|
-
{ :order => "#{search.fields[value.to_sym][:attribute]} DESC" }
|
78
|
-
end
|
79
72
|
end
|
80
73
|
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: birddog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.9.p1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brandon Dewitt
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-05 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
16
|
-
requirement: &
|
16
|
+
requirement: &17260400 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *17260400
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &17259880 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *17259880
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3-ruby
|
38
|
-
requirement: &
|
38
|
+
requirement: &17259400 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *17259400
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: pry
|
49
|
-
requirement: &
|
49
|
+
requirement: &17245300 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *17245300
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: chronic
|
60
|
-
requirement: &
|
60
|
+
requirement: &17244840 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *17244840
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: activerecord
|
71
|
-
requirement: &
|
71
|
+
requirement: &17244300 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *17244300
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: activesupport
|
82
|
-
requirement: &
|
82
|
+
requirement: &17243780 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,18 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :runtime
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
91
|
-
- !ruby/object:Gem::Dependency
|
92
|
-
name: squeel
|
93
|
-
requirement: &19662480 !ruby/object:Gem::Requirement
|
94
|
-
none: false
|
95
|
-
requirements:
|
96
|
-
- - ! '>='
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
version: '0'
|
99
|
-
type: :runtime
|
100
|
-
prerelease: false
|
101
|
-
version_requirements: *19662480
|
90
|
+
version_requirements: *17243780
|
102
91
|
description: Seeeeeeeee Readme
|
103
92
|
email:
|
104
93
|
- brandonsdewitt@gmail.com
|
@@ -117,6 +106,7 @@ files:
|
|
117
106
|
- lib/birddog/date_expression.rb
|
118
107
|
- lib/birddog/field_conditions.rb
|
119
108
|
- lib/birddog/numeric_expression.rb
|
109
|
+
- lib/birddog/scope_builder.rb
|
120
110
|
- lib/birddog/searchable.rb
|
121
111
|
- lib/birddog/version.rb
|
122
112
|
- specs/birddog_spec.rb
|
@@ -139,16 +129,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
129
|
version: '0'
|
140
130
|
segments:
|
141
131
|
- 0
|
142
|
-
hash:
|
132
|
+
hash: 2755655051036560996
|
143
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
134
|
none: false
|
145
135
|
requirements:
|
146
|
-
- - ! '
|
136
|
+
- - ! '>'
|
147
137
|
- !ruby/object:Gem::Version
|
148
|
-
version:
|
149
|
-
segments:
|
150
|
-
- 0
|
151
|
-
hash: 1270257218013225858
|
138
|
+
version: 1.3.1
|
152
139
|
requirements: []
|
153
140
|
rubyforge_project: birddog
|
154
141
|
rubygems_version: 1.8.10
|