forty_facets 0.0.3 → 0.0.4

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: 3e439d4f05f7a8bfe44665eec3bed14cf9d323e2
4
- data.tar.gz: c2c5a838fe3489158e5a88947e30cedfe68472b0
3
+ metadata.gz: 25afbbdd35eee3919efcdbf6766ef3173ad1a2fe
4
+ data.tar.gz: 864056c9c65d99a49a890177d7ff87440a18d62b
5
5
  SHA512:
6
- metadata.gz: 35018e26f1a6a037cf48d9e48d5df2fcd4df016758b4a5f91314b5f1b7ae6d36c5c91e6bd85d4f372417447fca70545469be79c2f6b8ff74aa94309eecf2d273
7
- data.tar.gz: 337b7d8da1cfb036564bb03b1e997e0fd4de8d705d3efab261ec5bd5cc657f21f62a8c0a6f3b0c54b86f377644c35b0e8435ef0dfd7c4ed31555911965306989
6
+ metadata.gz: 6f40a9650e2b73e552092a810e33c06390f81f554185e6d8af4749b881828ed5065a06e013ca1956b08eff121920bf19b613598a931cbc1fd60fe687da915187
7
+ data.tar.gz: 2e49716fd8a9b30ffe935d49976f2175068507c2c4448996913b2c08ea1e10e9714a3c91265560e3d573f0804f13c9100effc31a317f654418df3a20517d67e5
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Build Status](https://travis-ci.org/fortytools/forty_facets.svg)](https://travis-ci.org/fortytools/forty_facets)
2
+ [![Coverage Status](https://coveralls.io/repos/fortytools/forty_facets/badge.png?branch=master)](https://coveralls.io/r/fortytools/forty_facets?branch=master)
3
+ [![Code Climate](https://codeclimate.com/github/fortytools/forty_facets.png)](https://codeclimate.com/github/fortytools/forty_facets)
4
+
1
5
  # FortyFacets
2
6
 
3
7
  FortyFacets lets you easily build explorative search interfaces based on fields of your ActiveRecord models.
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/*_test.rb"
7
+ end
8
+
9
+ task :default => :test
data/forty_facets.gemspec CHANGED
@@ -20,4 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.5"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "sqlite3"
25
+ spec.add_development_dependency "coveralls"
26
+ spec.add_development_dependency "activerecord", ">= 4.0"
23
27
  end
@@ -1,203 +1,38 @@
1
1
  module FortyFacets
2
+ # Inherit this class to create a custom search for your model
3
+ #
4
+ # class MovieSearch < FortyFacets::FacetSearch
5
+ # model 'Movie'
6
+ #
7
+ # text :title
8
+ # range :price
9
+ # facet :genre, name: 'Genre'
10
+ # facet :year, name: 'Releaseyear', order: :year
11
+ # facet :studio, name: 'Studio', order: :name
12
+ #
13
+ # orders 'Title' => :title,
14
+ # 'price, cheap first' => "price asc",
15
+ # 'price, expensive first' => {price: :desc, title: :desc}
16
+ #
17
+ # end
2
18
  class FacetSearch
3
19
  attr_reader :filters, :orders
4
20
 
5
-
6
- FieldDefinition = Struct.new(:search, :model_field, :options) do
7
- def request_param
8
- model_field
9
- end
10
- end
11
-
12
- Filter = Struct.new(:field_definition, :search_instance, :value) do
13
- def name
14
- field_definition.options[:name] || field_definition.model_field
15
- end
16
-
17
- def empty?
18
- value.nil? || value == '' || value == []
19
- end
20
-
21
- # generate a search with this filter removed
22
- def without
23
- search = search_instance
24
- return search if empty?
25
- new_params = search_instance.params
26
- new_params.delete(field_definition.request_param)
27
- search_instance.class.new_unwrapped(new_params)
28
- end
29
- end
30
-
31
- class RangeField < FieldDefinition
32
- class RangeFilter < Filter
33
- def build_scope
34
- return Proc.new { |base| base } if empty?
35
- Proc.new { |base| base.where("#{field_definition.model_field} >= ? AND #{field_definition.model_field} <= ? ", min_value, max_value ) }
36
- end
37
-
38
- def min_value
39
- return nil if empty?
40
- value.split(' - ').first
41
- end
42
-
43
- def max_value
44
- return nil if empty?
45
- value.split(' - ').last
46
- end
47
-
48
- def absolute_interval
49
- @abosultes ||= without.result.select("min(#{field_definition.model_field}) as min, max(#{field_definition.model_field}) as max").first
50
- end
51
-
52
- def absolute_min
53
- absolute_interval.min
54
- end
55
-
56
- def absolute_max
57
- absolute_interval.max
58
- end
59
-
60
- end
61
-
62
- def build_filter(search_instance, value)
63
- RangeFilter.new(self, search_instance, value)
64
- end
65
- end
66
-
67
- class TextField < FieldDefinition
68
- class TextFilter < Filter
69
- def build_scope
70
- return Proc.new { |base| base } if empty?
71
- like_value = expression_value(value)
72
- Proc.new { |base| base.where("#{field_definition.model_field} like ?", like_value ) }
73
- end
74
-
75
- def expression_value(term)
76
- if field_definition.options[:prefix]
77
- "#{term}%"
78
- else
79
- "%#{term}%"
80
- end
81
- end
82
- end
83
-
84
- def build_filter(search_instance, value)
85
- TextFilter.new(self, search_instance, value)
86
- end
87
- end
88
-
89
- class FacetField < FieldDefinition
90
- FacetValue = Struct.new(:entity, :count, :selected)
91
-
92
- class FacetFilter < Filter
93
- def association
94
- field_definition.search.root_class.reflect_on_association(field_definition.model_field)
95
- end
96
-
97
- # class objects in this filter
98
- def klass
99
- association.klass
100
- end
101
-
102
- def values
103
- @values ||= Array.wrap(value).sort.uniq
104
- end
105
-
106
- def selected
107
- @selected ||= klass.find(values)
108
- end
109
-
110
- def build_scope
111
- return Proc.new { |base| base } if empty?
112
- Proc.new { |base| base.where(association.association_foreign_key => values) }
113
- end
114
-
115
- def facet
116
- my_column = association.association_foreign_key
117
- counts = without.result.select("#{my_column} as foreign_id, count(#{my_column}) as occurrences").group(my_column)
118
- entities_by_id = klass.find(counts.map(&:foreign_id)).group_by(&:id)
119
- facet = counts.inject([]) do |sum, count|
120
- facet_entity = entities_by_id[count.foreign_id].first
121
- is_selected = selected.include?(facet_entity)
122
- sum << FacetValue.new(facet_entity, count.occurrences, is_selected)
123
- end
124
-
125
- order_accessor = field_definition.options[:order]
126
- if order_accessor
127
- facet.sort_by!{|facet_value| facet_value.entity.send(order_accessor) }
128
- else
129
- facet.sort_by!{|facet_value| -facet_value.count }
130
- end
131
- facet
132
-
133
- end
134
-
135
- def without
136
- new_params = search_instance.params || {}
137
- new_params.delete(field_definition.request_param)
138
- search_instance.class.new_unwrapped(new_params)
139
- end
140
-
141
- def remove(value)
142
- new_params = search_instance.params || {}
143
- old_values = new_params[field_definition.request_param]
144
- old_values.delete(value.id.to_s)
145
- new_params.delete(field_definition.request_param) if old_values.empty?
146
- search_instance.class.new_unwrapped(new_params)
147
- end
148
-
149
- def add(entity)
150
- new_params = search_instance.params || {}
151
- old_values = new_params[field_definition.request_param] ||= []
152
- old_values << entity.id.to_s
153
- search_instance.class.new_unwrapped(new_params)
154
- end
155
-
156
- end
157
-
158
- def build_filter(search_instance, param_value)
159
- FacetFilter.new(self, search_instance, param_value)
160
- end
161
-
162
- end
163
-
164
21
  class << self
165
22
  def model(model_name)
166
23
  @model_name = model_name
167
24
  end
168
25
 
169
26
  def text(model_field, opts = {})
170
- definitions << TextField.new(self, model_field, opts)
27
+ definitions << TextFilterDefinition.new(self, model_field, opts)
171
28
  end
172
29
 
173
30
  def range(model_field, opts = {})
174
- definitions << RangeField.new(self, model_field, opts)
31
+ definitions << RangeFilterDefintion.new(self, model_field, opts)
175
32
  end
176
33
 
177
34
  def facet(model_field, opts = {})
178
- definitions << FacetField.new(self, model_field, opts)
179
- end
180
-
181
- OrderDefinition = Struct.new(:title, :clause) do
182
- def build(search, order_param)
183
- Order.new(search, self, order_param == title.to_s)
184
- end
185
-
186
- def request_value
187
- title
188
- end
189
- end
190
-
191
- Order = Struct.new(:search, :definition, :active) do
192
- def title
193
- definition.title
194
- end
195
-
196
- def by
197
- new_params = search.params || {}
198
- new_params[:order] = definition.request_value
199
- search.class.new_unwrapped(new_params)
200
- end
35
+ definitions << BelongsToFilter.new(self, model_field, opts)
201
36
  end
202
37
 
203
38
  def orders(name_and_order_options)
@@ -226,16 +61,12 @@ module FortyFacets
226
61
  end
227
62
 
228
63
  def order_definitions
229
- @order_definitions
64
+ @order_definitions ||= []
230
65
  end
231
66
  end
232
67
 
233
- def initialize(request_params)
234
- params = if request_params && request_params[self.class.request_param_name]
235
- request_params[self.class.request_param_name]
236
- else
237
- {}
238
- end
68
+ def initialize(request_params = {})
69
+ params = request_to_search_params(request_params)
239
70
  @filters = self.class.definitions.inject([]) do |filters, definition|
240
71
  filters << definition.build_filter(self, params[definition.request_param])
241
72
  end
@@ -251,7 +82,7 @@ module FortyFacets
251
82
  end
252
83
 
253
84
  def filter(filter_name)
254
- filter = @filters.find { |f| f.field_definition.model_field == filter_name }
85
+ filter = @filters.find { |f| f.filter_definition.model_field == filter_name }
255
86
  raise "unknown filter #{filter_name}" unless filter
256
87
  filter
257
88
  end
@@ -274,7 +105,7 @@ module FortyFacets
274
105
 
275
106
  def params
276
107
  params = @filters.inject({}) do |sum, filter|
277
- sum[filter.field_definition.request_param] = filter.value.dup unless filter.empty?
108
+ sum[filter.filter_definition.request_param] = filter.value.dup unless filter.empty?
278
109
  sum
279
110
  end
280
111
  params[:order] = order.definition.request_value if order
@@ -288,6 +119,17 @@ module FortyFacets
288
119
  def unfiltered?
289
120
  @filters.reject(&:empty?).empty?
290
121
  end
122
+
123
+ private
124
+
125
+ def request_to_search_params(request_params)
126
+ if request_params && request_params[self.class.request_param_name]
127
+ request_params[self.class.request_param_name]
128
+ else
129
+ {}
130
+ end
131
+ end
132
+
291
133
  end
292
134
  end
293
135
 
@@ -0,0 +1,80 @@
1
+ module FortyFacets
2
+ class BelongsToFilter < FilterDefinition
3
+ FacetValue = Struct.new(:entity, :count, :selected)
4
+
5
+ class FacetFilter < Filter
6
+ def association
7
+ filter_definition.search.root_class.reflect_on_association(filter_definition.model_field)
8
+ end
9
+
10
+ # class objects in this filter
11
+ def klass
12
+ association.klass
13
+ end
14
+
15
+ def values
16
+ @values ||= Array.wrap(value).sort.uniq
17
+ end
18
+
19
+ def selected
20
+ @selected ||= klass.find(values)
21
+ end
22
+
23
+ def build_scope
24
+ return Proc.new { |base| base } if empty?
25
+ Proc.new { |base| base.where(association.association_foreign_key => values) }
26
+ end
27
+
28
+ def facet
29
+ my_column = association.association_foreign_key
30
+ counts = without.result.select("#{my_column} as foreign_id, count(#{my_column}) as occurrences").group(my_column)
31
+ entities_by_id = klass.find(counts.map(&:foreign_id)).group_by(&:id)
32
+ facet = counts.inject([]) do |sum, count|
33
+ facet_entity = entities_by_id[count.foreign_id].first
34
+ is_selected = selected.include?(facet_entity)
35
+ sum << FacetValue.new(facet_entity, count.occurrences, is_selected)
36
+ end
37
+
38
+ order_facet!(facet)
39
+ end
40
+
41
+ def without
42
+ new_params = search_instance.params || {}
43
+ new_params.delete(filter_definition.request_param)
44
+ search_instance.class.new_unwrapped(new_params)
45
+ end
46
+
47
+ def remove(value)
48
+ new_params = search_instance.params || {}
49
+ old_values = new_params[filter_definition.request_param]
50
+ old_values.delete(value.id.to_s)
51
+ new_params.delete(filter_definition.request_param) if old_values.empty?
52
+ search_instance.class.new_unwrapped(new_params)
53
+ end
54
+
55
+ def add(entity)
56
+ new_params = search_instance.params || {}
57
+ old_values = new_params[filter_definition.request_param] ||= []
58
+ old_values << entity.id.to_s
59
+ search_instance.class.new_unwrapped(new_params)
60
+ end
61
+
62
+ private
63
+
64
+ def order_facet!(facet)
65
+ order_accessor = filter_definition.options[:order]
66
+ if order_accessor
67
+ facet.sort_by!{|facet_value| facet_value.entity.send(order_accessor) }
68
+ else
69
+ facet.sort_by!{|facet_value| -facet_value.count }
70
+ end
71
+ facet
72
+ end
73
+ end
74
+
75
+ def build_filter(search_instance, param_value)
76
+ FacetFilter.new(self, search_instance, param_value)
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ module FortyFacets
2
+ class RangeFilterDefintion < FilterDefinition
3
+ class RangeFilter < Filter
4
+ def build_scope
5
+ return Proc.new { |base| base } if empty?
6
+ Proc.new { |base| base.where("#{filter_definition.model_field} >= ? AND #{filter_definition.model_field} <= ? ", min_value, max_value ) }
7
+ end
8
+
9
+ def min_value
10
+ return nil if empty?
11
+ value.split(' - ').first
12
+ end
13
+
14
+ def max_value
15
+ return nil if empty?
16
+ value.split(' - ').last
17
+ end
18
+
19
+ def absolute_interval
20
+ @abosultes ||= without.result.select("min(#{filter_definition.model_field}) as min, max(#{filter_definition.model_field}) as max").first
21
+ end
22
+
23
+ def absolute_min
24
+ absolute_interval.min
25
+ end
26
+
27
+ def absolute_max
28
+ absolute_interval.max
29
+ end
30
+
31
+ end
32
+
33
+ def build_filter(search_instance, value)
34
+ RangeFilter.new(self, search_instance, value)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ module FortyFacets
2
+ class TextFilterDefinition < FilterDefinition
3
+ class TextFilter < Filter
4
+ def build_scope
5
+ return Proc.new { |base| base } if empty?
6
+ like_value = expression_value(value)
7
+ Proc.new { |base| base.where("#{filter_definition.model_field} like ?", like_value ) }
8
+ end
9
+
10
+ def expression_value(term)
11
+ if filter_definition.options[:prefix]
12
+ "#{term}%"
13
+ else
14
+ "%#{term}%"
15
+ end
16
+ end
17
+ end
18
+
19
+ def build_filter(search_instance, value)
20
+ TextFilter.new(self, search_instance, value)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module FortyFacets
2
+ # Base class for the objects representing a specific value for a specific
3
+ # type of filter. Most FilterDefinitions will have their own Filter subclass
4
+ # to control values for display and rendering to request parameters.
5
+ Filter = Struct.new(:filter_definition, :search_instance, :value) do
6
+ def name
7
+ filter_definition.options[:name] || filter_definition.model_field
8
+ end
9
+
10
+ def empty?
11
+ value.nil? || value == '' || value == []
12
+ end
13
+
14
+ # generate a search with this filter removed
15
+ def without
16
+ search = search_instance
17
+ return search if empty?
18
+ new_params = search_instance.params
19
+ new_params.delete(filter_definition.request_param)
20
+ search_instance.class.new_unwrapped(new_params)
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,9 @@
1
+ module FortyFacets
2
+ # Base class for the classes storing the definition of differently behaving filters
3
+ FilterDefinition = Struct.new(:search, :model_field, :options) do
4
+ def request_param
5
+ model_field
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,16 @@
1
+ module FortyFacets
2
+ # Represents the ordering for a specific search
3
+ Order = Struct.new(:search, :definition, :active) do
4
+ def title
5
+ definition.title
6
+ end
7
+
8
+ # Returns a search with the same filter ordered by this criteria
9
+ def by
10
+ new_params = search.params || {}
11
+ new_params[:order] = definition.request_value
12
+ search.class.new_unwrapped(new_params)
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,15 @@
1
+ module FortyFacets
2
+ # Stores the parameters of a order criteria for a search.
3
+ OrderDefinition = Struct.new(:title, :clause) do
4
+ def build(search, order_param)
5
+ Order.new(search, self, order_param == title.to_s)
6
+ end
7
+
8
+ # Returns the value that is used in the request parameter to indicate that
9
+ # the search result is ordered by this criteria.
10
+ def request_value
11
+ title
12
+ end
13
+ end
14
+ end
15
+
@@ -1,3 +1,3 @@
1
1
  module FortyFacets
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/forty_facets.rb CHANGED
@@ -3,4 +3,11 @@ require "forty_facets/version"
3
3
  module FortyFacets
4
4
  end
5
5
 
6
+ require "forty_facets/order_definition"
7
+ require "forty_facets/order"
8
+ require "forty_facets/filter"
9
+ require "forty_facets/filter_definition"
10
+ require "forty_facets/filter/belongs_to"
11
+ require "forty_facets/filter/range_filter_definition"
12
+ require "forty_facets/filter/text_filter_definition"
6
13
  require "forty_facets/facet_search"
@@ -0,0 +1,80 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require "minitest/autorun"
5
+ require 'active_record'
6
+ require 'logger'
7
+ require_relative '../lib/forty_facets'
8
+
9
+ silence_warnings do
10
+ ActiveRecord::Migration.verbose = false
11
+ ActiveRecord::Base.logger = Logger.new(nil)
12
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
13
+ end
14
+
15
+ ActiveRecord::Base.connection.instance_eval do
16
+
17
+ create_table :studios do |t|
18
+ t.string :name
19
+ end
20
+
21
+ create_table :movies do |t|
22
+ t.integer :studio_id
23
+ t.string :title
24
+ t.float :price
25
+ end
26
+
27
+ end
28
+
29
+ class Studio < ActiveRecord::Base
30
+ end
31
+
32
+ class Movie < ActiveRecord::Base
33
+ belongs_to :studio
34
+ end
35
+
36
+ class MovieSearch < FortyFacets::FacetSearch
37
+ model 'Movie'
38
+
39
+ text :title, name: 'Title'
40
+ facet :studio, name: 'Studio'
41
+ range :price, name: 'Price'
42
+ end
43
+
44
+ studios = []
45
+ %w{A B C D}.each do |suffix|
46
+ studios << Studio.create!(name: "Studio #{suffix}")
47
+ end
48
+
49
+ rand = Random.new
50
+ %w{Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren}.each_with_index do |title, index|
51
+ Movie.create!(title: title, studio: studios[index % studios.length], price: rand.rand(20.0))
52
+ end
53
+
54
+ class SmokeTest < Minitest::Test
55
+
56
+ def test_it_finds_all_movies
57
+ search = MovieSearch.new({})
58
+ assert_equal Movie.all.size, search.result.size
59
+ end
60
+
61
+ def test_text_filter
62
+ search = MovieSearch.new({'search' => { title: 'ipsum' }})
63
+ assert_equal 1, search.result.size
64
+ assert_equal 'ipsum', search.result.first.title
65
+ end
66
+
67
+ def test_belongs_to_filter
68
+ blank_search = MovieSearch.new
69
+ first_facet_value = blank_search.filter(:studio).facet.first
70
+ studio = first_facet_value.entity
71
+ assert_kind_of Studio, studio
72
+
73
+ movies_with_studio = Movie.where(studio: studio)
74
+ search_with_studio = blank_search.filter(:studio).add(studio)
75
+
76
+ assert_equal movies_with_studio.size, search_with_studio.result.size
77
+ assert_equal movies_with_studio.size, first_facet_value.count
78
+ end
79
+
80
+ end
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forty_facets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Axel Tetzlaff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-19 00:00:00.000000000 Z
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,62 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '4.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
41
97
  description: FortyFacets lets you easily build explorative search interfaces based
42
98
  on fields of your active_record models.
43
99
  email:
@@ -46,7 +102,9 @@ executables: []
46
102
  extensions: []
47
103
  extra_rdoc_files: []
48
104
  files:
105
+ - ".coveralls.yml"
49
106
  - ".gitignore"
107
+ - ".travis.yml"
50
108
  - Gemfile
51
109
  - LICENSE.txt
52
110
  - README.md
@@ -54,7 +112,16 @@ files:
54
112
  - forty_facets.gemspec
55
113
  - lib/forty_facets.rb
56
114
  - lib/forty_facets/facet_search.rb
115
+ - lib/forty_facets/filter.rb
116
+ - lib/forty_facets/filter/belongs_to.rb
117
+ - lib/forty_facets/filter/range_filter_definition.rb
118
+ - lib/forty_facets/filter/text_filter_definition.rb
119
+ - lib/forty_facets/filter_definition.rb
120
+ - lib/forty_facets/order.rb
121
+ - lib/forty_facets/order_definition.rb
57
122
  - lib/forty_facets/version.rb
123
+ - test/smoke_test.rb
124
+ - test/test_helper.rb
58
125
  homepage: https://github.com/fortytools/forty_facets
59
126
  licenses:
60
127
  - MIT
@@ -79,4 +146,6 @@ rubygems_version: 2.2.0
79
146
  signing_key:
80
147
  specification_version: 4
81
148
  summary: Library for building facet searches for active_record models
82
- test_files: []
149
+ test_files:
150
+ - test/smoke_test.rb
151
+ - test/test_helper.rb