filternator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d22429121aad479f8d792514084bd1c8acfe74d
4
+ data.tar.gz: 8838cee850318633224c5ab008bfef59ea9eccf2
5
+ SHA512:
6
+ metadata.gz: 9e9cf6ad40c9615dde262f23ca9ee377fd6f160db9cb2435ecbb900257df7c55f2151b9fd474c83f5c7e28241356687f102f07d0cb430272980a3f8e6f77d648
7
+ data.tar.gz: 7ff7ae605d2af28ba557f6a4c97ab8d18c63530b5903b4c38f9d5bdd4773dbf153b6422f7f56bec01df5e8a3f3de0b28b6c59c6ba85ab4a5dcf47565efade4b0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in filternator.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # Filternator
2
+
3
+ A very basic gem for generating JSON responses for collections.
4
+
5
+ Currently supported:
6
+
7
+ * Pagination (via will_paginate)
8
+ * Simple Filters
9
+
10
+ Not supported:
11
+
12
+ * Combining multiple filters
13
+ * Filters with parameters
14
+
15
+ We use this gem in production, but it's uses are very specific to what we use. YMMV.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'filternator'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install filternator
30
+
31
+ ## Usage
32
+
33
+ Given a model:
34
+
35
+ ``` ruby
36
+ class User < ActiveRecord::Base
37
+
38
+ def self.all_filters
39
+ %w(all paying beta_users)
40
+ end
41
+
42
+ def self.paying
43
+ where(paying: true)
44
+ end
45
+
46
+ def self.beta_users
47
+ where(beta: true)
48
+ end
49
+
50
+ def self.visible
51
+ where(visible: true)
52
+ end
53
+
54
+ end
55
+ ```
56
+
57
+ Then you can create a filter in the controller:
58
+
59
+ ``` ruby
60
+ def index
61
+ render json: filter.apply(params)
62
+ end
63
+
64
+ private
65
+
66
+ def filter
67
+ Filternator.new(scope: User.visible)
68
+ end
69
+ ```
70
+
71
+ And you can execute it, like this:
72
+
73
+ ``` ruby
74
+ get :index, filter: "all", "page" => 2
75
+ expect(response.body).to eq({
76
+ users: [
77
+ {
78
+ id: 31,
79
+ email: "jennifer@example.org"
80
+ # etc...
81
+ }
82
+ },
83
+ meta: {
84
+ filters: ["all", "paying", "beta_users"],
85
+ applied_filter: "all",
86
+ pagination: {
87
+ total: 31,
88
+ total_pages: 2,
89
+ first_page: false,
90
+ last_page: true,
91
+ # and some more
92
+ }
93
+ }
94
+ }.to_json)
95
+ ```
96
+
97
+ ### Pagination
98
+
99
+ You can pass the pagination options directly into will_paginate's view helper.
100
+
101
+ ``` erb
102
+ <%= will_paginate OpenStruct.new(@user_response.body["meta"]["pagination"]) %>
103
+ ```
104
+
105
+ (you should probably clean this up, but you get the point)
106
+
107
+ ### Stats
108
+
109
+ You can also generate an overview of the counts of each filter.
110
+
111
+ ``` ruby
112
+ # in your controller
113
+ def stats
114
+ render json: filter.stats
115
+ end
116
+ ```
117
+
118
+ Then the output looks something like this:
119
+
120
+ ``` ruby
121
+ get :stats
122
+ expect(response.body).to eq({
123
+ all: 31,
124
+ paying: 4,
125
+ beta_users: 13,
126
+ }.to_json)
127
+ ```
128
+
129
+ ## Configuration
130
+
131
+ There are a bunch of ways to configure the filter.
132
+
133
+ ### Presenters
134
+
135
+ Use a presenter to change the output of the collection, by giving something that
136
+ can be mapped on each item.
137
+
138
+ ``` ruby
139
+ UserDecorator = Struct.new(:user) do
140
+
141
+ def self.to_proc
142
+ method(:new).to_proc
143
+ end
144
+
145
+ def as_json(*)
146
+ { name: user.name, email: user.email }
147
+ end
148
+
149
+ end
150
+
151
+ filter = Filternator.new(scope: User, presenter: UserDecorator)
152
+ ```
153
+
154
+ ### Scope name
155
+
156
+ By default, the name of the results is the plural of the model's name. You can
157
+ change it by providing the `:scope_name` option.
158
+
159
+ ``` ruby
160
+ filter = Filternator.new(scope: User.admins, scope_name: "administrators")
161
+ ```
162
+
163
+ ### All filters
164
+
165
+ In the example mentioned above, I defined the list of available methods on the
166
+ class. You can also change the available filters per filterer, by supplying the
167
+ `:all_filters` option.
168
+
169
+ ``` ruby
170
+ # prohibits the access to the `paying`-scope.
171
+ filter = Filternator.new(scope: User, all_filters: %w(all beta_users))
172
+ ```
173
+
174
+ ### Default filter
175
+
176
+ Note for ActiveRecord 3: `all` will not return a scope, but an array and will
177
+ not work. You use `.scoped` instead. You can also override `.all` to return
178
+ `.scoped`, but that might break some of your code. If you choose a different
179
+ name than `all`, you need to specify it, and allow it with `all_filters`.
180
+
181
+ ``` ruby
182
+ filter = Filternator.new(scope: User, default_filter: "scoped", all_filters: %w(scoped other))
183
+ ```
184
+
185
+
186
+ ## Contributing
187
+
188
+ 1. Fork it
189
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
190
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
191
+ 4. Push to the branch (`git push origin my-new-feature`)
192
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = "spec"
7
+ t.rspec_opts = "--format progress"
8
+ end
9
+ rescue LoadError
10
+ task :spec do
11
+ puts "RSpec not installed"
12
+ exit 1
13
+ end
14
+ end
15
+
16
+ task :default => %w(spec)
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'filternator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "filternator"
8
+ spec.version = Filternator::VERSION
9
+ spec.authors = ["iain"]
10
+ spec.email = ["iain@iain.nl"]
11
+ spec.description = %q{A very basic gem for generating JSON responses for collections.}
12
+ spec.summary = %q{A very basic gem for generating JSON responses for collections.}
13
+ spec.homepage = "https://github.com/yourkarma/filternator"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", ">= 3.1"
22
+ spec.add_dependency "will_paginate", ">= 3.0"
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "sqlite3"
27
+ end
@@ -0,0 +1,60 @@
1
+ require "forwardable"
2
+
3
+ module Filternator
4
+ class ApplyFilter
5
+ extend Forwardable
6
+
7
+ attr_reader :filterer, :params
8
+
9
+ def initialize(filterer, params)
10
+ @filterer = filterer
11
+ @params = params
12
+ end
13
+
14
+ def_delegators :filterer, :scope_name, :scope, :all_filters, :presenter, :default_filter
15
+
16
+ def as_json(*args)
17
+ {
18
+ scope_name => presenters.as_json(*args),
19
+ :meta => {
20
+ :filters => all_filters,
21
+ :applied_filter => filter,
22
+ :pagination => pagination.as_json,
23
+ },
24
+ }
25
+ end
26
+
27
+ def count
28
+ filtered_scope.count
29
+ end
30
+
31
+ def presenters
32
+ paginated_scope.map(&presenter)
33
+ end
34
+
35
+ def pagination
36
+ Pagination.new(paginated_scope)
37
+ end
38
+
39
+ def paginated_scope
40
+ filtered_scope.paginate(page: params[:page])
41
+ end
42
+
43
+ def filter
44
+ params[:filter].presence || default_filter
45
+ end
46
+
47
+ def filtered_scope
48
+ if valid_filter?
49
+ scope.public_send(filter)
50
+ else
51
+ scope
52
+ end
53
+ end
54
+
55
+ def valid_filter?
56
+ filter.present? && all_filters.include?(filter)
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ module Filternator
2
+ class Filter
3
+
4
+ attr_reader :scope, :scope_name, :presenter, :all_filters, :default_filter
5
+
6
+ def initialize(options = {})
7
+ @scope = options.fetch(:scope)
8
+ @scope_name = options.fetch(:scope_name) { derive_scope_name }
9
+ @presenter = options.fetch(:presenter) { null_presenter }
10
+ @all_filters = options.fetch(:all_filters) { scope.all_filters }
11
+ @default_filter = options.fetch(:default_filter) { "all" }
12
+ end
13
+
14
+ def stats
15
+ Hash[ all_filters.map { |filter| [ filter, apply(filter: filter).count ] } ]
16
+ end
17
+
18
+ def apply(params)
19
+ ApplyFilter.new(self, params)
20
+ end
21
+
22
+ private
23
+
24
+ def derive_scope_name
25
+ scope.model_name.element.pluralize
26
+ end
27
+
28
+ def null_presenter
29
+ lambda { |item| item }
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module Filternator
2
+ class Pagination
3
+
4
+ attr_reader :scope
5
+
6
+ def initialize(scope)
7
+ @scope = scope
8
+ end
9
+
10
+ def as_json(*)
11
+ {
12
+ total: scope.total_entries,
13
+ total_pages: scope.total_pages,
14
+ first_page: scope.current_page == 1,
15
+ last_page: scope.next_page.blank?,
16
+ current_page: scope.current_page,
17
+ previous_page: scope.previous_page,
18
+ next_page: scope.next_page,
19
+ out_of_bounds: scope.out_of_bounds?,
20
+ offset: scope.offset
21
+ }
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Filternator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,12 @@
1
+ require "filternator/version"
2
+ require "filternator/filter"
3
+ require "filternator/apply_filter"
4
+ require "filternator/pagination"
5
+
6
+ module Filternator
7
+
8
+ def self.new(options = {})
9
+ Filter.new(options)
10
+ end
11
+
12
+ end
@@ -0,0 +1,66 @@
1
+ require "filternator"
2
+ require "active_record"
3
+ require "will_paginate/active_record"
4
+
5
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
6
+
7
+ ActiveRecord::Migration.create_table :widgets do |t|
8
+ t.string :state, null: false
9
+ end
10
+
11
+ class Widget < ActiveRecord::Base
12
+ def self.all_filters
13
+ %w(all busy)
14
+ end
15
+ scope :busy, -> { where state: "busy" }
16
+ end
17
+
18
+ describe Filternator do
19
+
20
+ before do
21
+ Widget.delete_all
22
+ end
23
+
24
+ example "a basic filter" do
25
+ Widget.create! id: 1, state: "idle"
26
+ Widget.create! id: 2, state: "busy"
27
+ filter = Filternator.new(scope: Widget)
28
+ result = filter.apply(filter: "busy")
29
+ expect(result.as_json).to eq(
30
+ {
31
+ "widgets" => [{
32
+ "id" => 2,
33
+ "state" => "busy",
34
+ }],
35
+ meta: {
36
+ filters: %w(all busy),
37
+ applied_filter: "busy",
38
+ pagination: {
39
+ total: 1,
40
+ total_pages: 1,
41
+ first_page: true,
42
+ last_page: true,
43
+ current_page: 1,
44
+ previous_page: nil,
45
+ next_page: nil,
46
+ out_of_bounds: false,
47
+ offset: 0,
48
+ }
49
+ }
50
+ }
51
+ )
52
+ end
53
+
54
+ example "stats" do
55
+ Widget.create! id: 1, state: "idle"
56
+ Widget.create! id: 2, state: "busy"
57
+ filter = Filternator.new(scope: Widget)
58
+ expect(filter.stats).to eq(
59
+ {
60
+ "all" => 2,
61
+ "busy" => 1,
62
+ }
63
+ )
64
+ end
65
+
66
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filternator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - iain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: will_paginate
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
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: rake
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: rspec
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: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: A very basic gem for generating JSON responses for collections.
98
+ email:
99
+ - iain@iain.nl
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .travis.yml
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - filternator.gemspec
112
+ - lib/filternator.rb
113
+ - lib/filternator/apply_filter.rb
114
+ - lib/filternator/filter.rb
115
+ - lib/filternator/pagination.rb
116
+ - lib/filternator/version.rb
117
+ - spec/integration_spec.rb
118
+ homepage: https://github.com/yourkarma/filternator
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.0.3
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: A very basic gem for generating JSON responses for collections.
142
+ test_files:
143
+ - spec/integration_spec.rb