birddog 0.0.8 → 0.0.9.p1

Sign up to get free protection for your applications and to get access to all the features.
data/birddog.gemspec CHANGED
@@ -27,5 +27,4 @@ Gem::Specification.new do |s|
27
27
  s.add_runtime_dependency "chronic"
28
28
  s.add_runtime_dependency "activerecord"
29
29
  s.add_runtime_dependency "activesupport"
30
- s.add_runtime_dependency "squeel"
31
30
  end
@@ -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, name),
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
- tokens = tokenize(query)
80
- tokens.inject(@model) do |model, (key,value)|
81
- key, value = "text_search", key if value.nil?
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
- def text_search(*fields)
87
- options = fields.extract_options!
88
- fields = fields.map { |f| "LOWER(#{f}) LIKE :value" }.join(" OR ")
95
+ ################## PRIVATES ####################
89
96
 
90
- define_scope "text_search" do |value|
91
- options.merge(:conditions => [fields, { :value => "%#{value.downcase}%" }])
92
- end
97
+ def aggregate_and_aliased?(aggregate)
98
+ aggregate && aggregate.respond_to?(:alias) && aggregate.alias
93
99
  end
94
-
95
- ################## PRIVATES ####################
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
- if conditional?(value)
107
- field[:options].merge(:having => setup_conditions(field, value))
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].merge(:conditions => setup_conditions(field, value))
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
- { field[:attribute] => value }
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
- [[split_tokens.shift, split_tokens.join(":")]]
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
- FieldConversions = {
6
- :integer => lambda{ |v| v.to_i },
7
- :decimal => lambda{ |v| v.to_f },
8
- :float => lambda{ |v| v.to_f }
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 field[:match_substring]
22
- value_to_search = "%#{value_to_search}%"
23
- end
10
+ if !strict_equality
11
+ if field[:match_substring]
12
+ search_con = "=~"
13
+ value_to_search = "%#{value_to_search}%"
14
+ end
24
15
 
25
- if field[:regex] && regexed?(value)
26
- # TODO check db driver to determine regex operator for DB (current is Postgres)
27
- search_con = " ~ ? "
28
- value_to_search = value[1..value.size-2]
29
- field_to_search = field[:attribute]
30
- elsif field[:wildcard] && value_to_search.include?("*")
31
- value_to_search.gsub!(/[\*]/, "%")
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
- [ "#{field_to_search} #{search_con} ", value_to_search ]
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 conditions_for_date(field, condition, value)
38
- [ "#{field[:attribute]} #{condition} ? ", value.value.strftime("%Y-%m-%d")]
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 conditions_for_numeric(field, condition, value)
42
- db_value = FieldConversions[field[:type]].call(value)
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
- case condition
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
- [ "ABS(#{field[:attribute]}) >= ? AND ABS(#{field[:attribute]}) < ?", db_value.abs.floor, (db_value.abs + 1).floor ]
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
- [ "#{field[:attribute]} #{condition} ? ", db_value]
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
@@ -1,3 +1,3 @@
1
1
  module Birddog
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9.p1"
3
3
  end
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"
@@ -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 => "SUM(products.value) AS total_product_value"
43
- search.field :insensitive_last_name, :attribute => "last_name", :case_sensitive => false
44
- search.field :substringed_last_name, :attribute => "last_name", :match_substring => true
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.8
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-03 00:00:00.000000000Z
12
+ date: 2012-03-05 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
16
- requirement: &19678920 !ruby/object:Gem::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: *19678920
24
+ version_requirements: *17260400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &19678160 !ruby/object:Gem::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: *19678160
35
+ version_requirements: *17259880
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sqlite3-ruby
38
- requirement: &19677700 !ruby/object:Gem::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: *19677700
46
+ version_requirements: *17259400
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: pry
49
- requirement: &19677240 !ruby/object:Gem::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: *19677240
57
+ version_requirements: *17245300
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: chronic
60
- requirement: &19676800 !ruby/object:Gem::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: *19676800
68
+ version_requirements: *17244840
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: activerecord
71
- requirement: &19676360 !ruby/object:Gem::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: *19676360
79
+ version_requirements: *17244300
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: activesupport
82
- requirement: &19663100 !ruby/object:Gem::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: *19663100
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: 1270257218013225858
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: '0'
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