forty_facets 0.0.5 → 0.0.6

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: 83edd5ef19658c58ba68e8a0e45a01f84e03703f
4
- data.tar.gz: 0134b19f051971d1cdba2aa1009517a1b61e60b3
3
+ metadata.gz: f4d7a0229978d1d9133b0082e3924554cd5a0cbf
4
+ data.tar.gz: b054dd26585014ffe9aa8a7851e9d30c4e43d130
5
5
  SHA512:
6
- metadata.gz: 342c89c8336bfdc51ccbc487ccd30d1eaa4de7caf0c2617d6d6da86291326bfd9d41c9a3dae0f0e4cee46988a16084f633b99bdab04428d79b5e6a2e77303dfe
7
- data.tar.gz: 74d86c743767e2a2f76a3d442e3ea60c3985f385ba6c91dd8e5a15fdefa5535381ebb3dd482dc46addbc66011e381f0e3aaff145691f8b17f0031129fc18fcaa
6
+ metadata.gz: 4936450a29cb55d55562e94894db107defda556f29dd3690d3421f893f814a416f76dafeb84d09148375bf405e613f1ec76d104ed248b18bc0aa647f91ea4a56
7
+ data.tar.gz: f331f6c08b3aab3e1c51e633ed628633dea49ffdfa839dc777e79dc818a7dda41b9cc0f8ddc8a3daef6932705c844bfc3e9b9be8f9603a0d8194a6753eb64a2a
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea
19
+
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in forty_facets.gemspec
4
4
  gemspec
5
+
data/README.md CHANGED
@@ -8,7 +8,8 @@ FortyFacets lets you easily build explorative search interfaces based on fields
8
8
 
9
9
  ![demo](demo.gif)
10
10
 
11
- Try a [working demo](http://forty-facets-demo.herokuapp.com/ "Testinstallation on heroku")!
11
+ See it implemented in a [example rails application](https://github.com/fortytools/forty_facets_demo) or
12
+ try a [working demo](http://forty-facets-demo.herokuapp.com/ "Testinstallation on heroku")!
12
13
 
13
14
  It offers a simple API to create an interactive UI to browse your data by iteratively adding
14
15
  filter values.
@@ -55,7 +56,7 @@ class HomeController < ApplicationController
55
56
  text :title # filter by a generic string entered by the user
56
57
  range :price, name: 'Price' # filter by ranges for decimal fields
57
58
  facet :genre, name: 'Genre' # generate a filter with all values of 'genre' occuring in the result
58
- facet :year, name: 'Releaseyear', order: :year # additionally oder values in the year field
59
+ facet :year, name: 'Releaseyear', order: :year # additionally order values in the year field
59
60
  facet :studio, name: 'Studio', order: :name
60
61
 
61
62
  orders 'Title' => :title,
data/lib/forty_facets.rb CHANGED
@@ -7,7 +7,8 @@ require "forty_facets/order_definition"
7
7
  require "forty_facets/order"
8
8
  require "forty_facets/filter"
9
9
  require "forty_facets/filter_definition"
10
- require "forty_facets/filter/belongs_to"
10
+ require "forty_facets/filter/belongs_to_filter_definition"
11
11
  require "forty_facets/filter/range_filter_definition"
12
12
  require "forty_facets/filter/text_filter_definition"
13
+ require "forty_facets/filter/attribute_filter_definition"
13
14
  require "forty_facets/facet_search"
@@ -28,11 +28,15 @@ module FortyFacets
28
28
  end
29
29
 
30
30
  def range(model_field, opts = {})
31
- definitions << RangeFilterDefintion.new(self, model_field, opts)
31
+ definitions << RangeFilterDefinition.new(self, model_field, opts)
32
32
  end
33
33
 
34
34
  def facet(model_field, opts = {})
35
- definitions << BelongsToFilter.new(self, model_field, opts)
35
+ definitions << BelongsToFilterDefinition.new(self, model_field, opts)
36
+ end
37
+
38
+ def facet_attr(model_field, opts = {})
39
+ definitions << AttributeFilterDefinition.new(self, model_field, opts)
36
40
  end
37
41
 
38
42
  def orders(name_and_order_options)
@@ -7,6 +7,10 @@ module FortyFacets
7
7
  filter_definition.options[:name] || filter_definition.model_field
8
8
  end
9
9
 
10
+ def values
11
+ @values ||= Array.wrap(value).sort.uniq
12
+ end
13
+
10
14
  def empty?
11
15
  value.nil? || value == '' || value == []
12
16
  end
@@ -15,7 +19,7 @@ module FortyFacets
15
19
  def without
16
20
  search = search_instance
17
21
  return search if empty?
18
- new_params = search_instance.params
22
+ new_params = search_instance.params || {}
19
23
  new_params.delete(filter_definition.request_param)
20
24
  search_instance.class.new_unwrapped(new_params)
21
25
  end
@@ -0,0 +1,44 @@
1
+ module FortyFacets
2
+ class AttributeFilterDefinition < FilterDefinition
3
+ class AttributeFilter < Filter
4
+ def selected
5
+ entity = search_instance.class.root_class
6
+ column = entity.columns_hash[filter_definition.model_field.to_s]
7
+ values.map{|v| column.type_cast(v)}
8
+ end
9
+
10
+ def build_scope
11
+ return Proc.new { |base| base } if empty?
12
+ Proc.new { |base| base.where(filter_definition.model_field => value) }
13
+ end
14
+
15
+ def facet
16
+ my_column = filter_definition.model_field
17
+ counts = without.result.reorder('').select("#{my_column} AS facet_value, count(#{my_column}) as occurrences").group(my_column)
18
+ counts.map do |c|
19
+ is_selected = selected.include?(c.facet_value)
20
+ FacetValue.new(c.facet_value, c.occurrences, is_selected)
21
+ end
22
+ end
23
+
24
+ def remove(value)
25
+ new_params = search_instance.params || {}
26
+ old_values = new_params[filter_definition.request_param]
27
+ old_values.delete(value.to_s)
28
+ new_params.delete(filter_definition.request_param) if old_values.empty?
29
+ search_instance.class.new_unwrapped(new_params)
30
+ end
31
+
32
+ def add(value)
33
+ new_params = search_instance.params || {}
34
+ old_values = new_params[filter_definition.request_param] ||= []
35
+ old_values << value.to_s
36
+ search_instance.class.new_unwrapped(new_params)
37
+ end
38
+ end
39
+
40
+ def build_filter(search_instance, value)
41
+ AttributeFilter.new(self, search_instance, value)
42
+ end
43
+ end
44
+ end
@@ -1,7 +1,5 @@
1
1
  module FortyFacets
2
- class BelongsToFilter < FilterDefinition
3
- FacetValue = Struct.new(:entity, :count, :selected)
4
-
2
+ class BelongsToFilterDefinition < FilterDefinition
5
3
  class FacetFilter < Filter
6
4
  def association
7
5
  filter_definition.search.root_class.reflect_on_association(filter_definition.model_field)
@@ -12,10 +10,6 @@ module FortyFacets
12
10
  association.klass
13
11
  end
14
12
 
15
- def values
16
- @values ||= Array.wrap(value).sort.uniq
17
- end
18
-
19
13
  def selected
20
14
  @selected ||= klass.find(values)
21
15
  end
@@ -29,25 +23,20 @@ module FortyFacets
29
23
  my_column = association.association_foreign_key
30
24
  counts = without.result.reorder('').select("#{my_column} as foreign_id, count(#{my_column}) as occurrences").group(my_column)
31
25
  entities_by_id = klass.find(counts.map(&:foreign_id)).group_by(&:id)
32
- facet = counts.inject([]) do |sum, count|
26
+
27
+ facet = counts.map do |count|
33
28
  facet_entity = entities_by_id[count.foreign_id].first
34
29
  is_selected = selected.include?(facet_entity)
35
- sum << FacetValue.new(facet_entity, count.occurrences, is_selected)
30
+ FacetValue.new(facet_entity, count.occurrences, is_selected)
36
31
  end
37
32
 
38
33
  order_facet!(facet)
39
34
  end
40
35
 
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)
36
+ def remove(entity)
48
37
  new_params = search_instance.params || {}
49
38
  old_values = new_params[filter_definition.request_param]
50
- old_values.delete(value.id.to_s)
39
+ old_values.delete(entity.id.to_s)
51
40
  new_params.delete(filter_definition.request_param) if old_values.empty?
52
41
  search_instance.class.new_unwrapped(new_params)
53
42
  end
@@ -1,5 +1,5 @@
1
1
  module FortyFacets
2
- class RangeFilterDefintion < FilterDefinition
2
+ class RangeFilterDefinition < FilterDefinition
3
3
  class RangeFilter < Filter
4
4
  def build_scope
5
5
  return Proc.new { |base| base } if empty?
@@ -1,6 +1,9 @@
1
1
  module FortyFacets
2
2
  # Base class for the classes storing the definition of differently behaving filters
3
3
  FilterDefinition = Struct.new(:search, :model_field, :options) do
4
+
5
+ FacetValue = Struct.new(:entity, :count, :selected)
6
+
4
7
  def request_param
5
8
  model_field
6
9
  end
@@ -1,3 +1,3 @@
1
1
  module FortyFacets
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
data/test/smoke_test.rb CHANGED
@@ -20,6 +20,7 @@ ActiveRecord::Base.connection.instance_eval do
20
20
 
21
21
  create_table :movies do |t|
22
22
  t.integer :studio_id
23
+ t.integer :year
23
24
  t.string :title
24
25
  t.float :price
25
26
  end
@@ -38,6 +39,7 @@ class MovieSearch < FortyFacets::FacetSearch
38
39
 
39
40
  text :title, name: 'Title'
40
41
  facet :studio, name: 'Studio'
42
+ facet_attr :year
41
43
  range :price, name: 'Price'
42
44
  end
43
45
 
@@ -48,7 +50,7 @@ end
48
50
 
49
51
  rand = Random.new
50
52
  %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))
53
+ Movie.create!(title: title, studio: studios[index % studios.length], price: rand.rand(20.0), year: (index%3 + 2010) )
52
54
  end
53
55
 
54
56
  class SmokeTest < Minitest::Test
@@ -64,6 +66,43 @@ class SmokeTest < Minitest::Test
64
66
  assert_equal 'ipsum', search.result.first.title
65
67
  end
66
68
 
69
+ def test_year_filter
70
+ search = MovieSearch.new({'search' => { year: '2011' }})
71
+ assert_equal [2011], search.result.map(&:year).uniq
72
+
73
+ facet = search.filter(:year).facet
74
+ assert_equal Movie.count, facet.map(&:count).sum
75
+ end
76
+
77
+ def test_year_add_remove_filter
78
+
79
+ search = MovieSearch.new()
80
+
81
+ search = search.filter(:year).add(2010)
82
+ assert_equal Movie.where(year: 2010).count, search.result.count
83
+
84
+ search = search.filter(:year).add(2011)
85
+ assert_equal Movie.where(year: [2010, 2011]).count, search.result.count
86
+
87
+ search = search.filter(:year).remove(2010)
88
+ assert_equal Movie.where(year: 2011).count, search.result.count
89
+ end
90
+
91
+ def test_selected_year_filter
92
+ search = MovieSearch.new()
93
+
94
+ search = search.filter(:year).add(2010)
95
+ assert_equal [2010], search.filter(:year).selected
96
+
97
+ search = search.filter(:year).add(2011)
98
+ assert_equal [2010, 2011], search.filter(:year).selected
99
+
100
+ facet = search.filter(:year).facet
101
+ assert facet.find{|fv| fv.entity == 2010}.selected
102
+ assert facet.find{|fv| fv.entity == 2011}.selected
103
+ assert !facet.find{|fv| fv.entity == 2012}.selected
104
+ end
105
+
67
106
  def test_belongs_to_filter
68
107
  blank_search = MovieSearch.new
69
108
  first_facet_value = blank_search.filter(:studio).facet.first
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.5
4
+ version: 0.0.6
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-28 00:00:00.000000000 Z
11
+ date: 2014-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,7 +113,8 @@ files:
113
113
  - lib/forty_facets.rb
114
114
  - lib/forty_facets/facet_search.rb
115
115
  - lib/forty_facets/filter.rb
116
- - lib/forty_facets/filter/belongs_to.rb
116
+ - lib/forty_facets/filter/attribute_filter_definition.rb
117
+ - lib/forty_facets/filter/belongs_to_filter_definition.rb
117
118
  - lib/forty_facets/filter/range_filter_definition.rb
118
119
  - lib/forty_facets/filter/text_filter_definition.rb
119
120
  - lib/forty_facets/filter_definition.rb