mongoid_filter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'mongoid'
4
+
5
+ # Specify your gem's dependencies in mongoid_filter.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Egor Lynko
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,59 @@
1
+ # MongoidFilter
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mongoid_filter'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mongoid_filter
18
+
19
+ ## Usage
20
+
21
+ ### Example
22
+
23
+ #### Model
24
+ ```ruby
25
+ class Log
26
+ include Mongoid::Document
27
+ include MongoidFilter
28
+
29
+ field :event_type, type: String
30
+ field :status, type: Boolean
31
+
32
+ # It will allow us to filter by this three fields
33
+ can_filter_by :event_type, :status, :created_at
34
+
35
+ # Specify custom method to create object from params
36
+ special_filter :created_at, ->(date) { Date.strptime(date, '%m/%d/%Y') }
37
+ end
38
+ ```
39
+ #### Controller
40
+ ```ruby
41
+ @logs = Log.where(user_id: user.id).filter_by(params[:search])
42
+ ```
43
+
44
+ #### View
45
+ ```erb
46
+ <%= form_for @request_logs.filter_form_object, as: :search do |f| %>
47
+ <%= f.select :event_type_eq, Log.event_types %>
48
+ <%= f.select :status_eq, Log.statuses %>
49
+ <%= f.text_area :created_at_gt %>
50
+ <% end %>
51
+ ```
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,93 @@
1
+ require 'mongoid_filter/version'
2
+ require 'mongoid_filter/form_object'
3
+
4
+ require 'ostruct'
5
+ require 'active_support/all'
6
+
7
+ module MongoidFilter
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ attr_writer :filter_fields, :special_filters, :filter_field_aliases
12
+
13
+ def filter_fields
14
+ @filter_fields ||= []
15
+ end
16
+
17
+ def special_filters
18
+ @special_filters ||= {}
19
+ end
20
+
21
+ def filter_field_aliases
22
+ @filter_aliases ||= {}
23
+ end
24
+
25
+ def can_filter_by(*fields)
26
+ self.filter_fields.concat(fields.flatten.map(&:to_sym))
27
+ end
28
+
29
+ def special_filter(field, deserializing_proc, options = {})
30
+ self.special_filters.merge!(field => deserializing_proc)
31
+
32
+ field_name = options[:field_name]
33
+ self.filter_field_aliases.merge!(field => field_name)
34
+ end
35
+
36
+ def filter_by(filter_params)
37
+ condition = {}
38
+ prepare_filter_params(filter_params).each do |attribute, value|
39
+ field_name, operator = parse_attribute(attribute)
40
+ condition.merge!(build_expression(field_name, operator, value)) if field_name.in? filter_fields
41
+ end
42
+ criteria = where(condition)
43
+ criteria.instance_variable_set(:@filter_form_object, FormObject.new(filter_params))
44
+ criteria
45
+ end
46
+
47
+ def filter_form_object
48
+ criteria.instance_variable_get(:@filter_form_object) || FormObject.new({})
49
+ end
50
+
51
+ private
52
+ def parse_attribute(attribute)
53
+ parts = attribute.split('_')
54
+ field_name = parts[0...-1].join('_').to_sym
55
+ operator = parts[-1].to_sym
56
+ [field_name, operator]
57
+ end
58
+
59
+ def build_expression(field_name, operator, value)
60
+ field = selector_field_name(field_name)
61
+ value = deserialize_value(field_name, value)
62
+ case operator
63
+ when :eq
64
+ {field => value}
65
+ when :gt, :lt, :gte, :lte
66
+ {field.send(operator) => value}
67
+ when :from
68
+ build_expression(field_name, :gte, value)
69
+ when :to
70
+ build_expression(field_name, :lte, value)
71
+ when :in
72
+ {field.in => value}
73
+ when :cont
74
+ regexp = Regexp.new(".*#{ Regexp.escape(value) }.*", "i")
75
+ {field => regexp}
76
+ else
77
+ {}
78
+ end
79
+ end
80
+
81
+ def prepare_filter_params(filter_params)
82
+ (filter_params || {}).select { |key, value| value.present? }.with_indifferent_access
83
+ end
84
+
85
+ def deserialize_value(field_name, value)
86
+ self.special_filters[field_name].try(:call, value) || value
87
+ end
88
+
89
+ def selector_field_name(field_name)
90
+ filter_field_aliases[field_name] || field_name
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,19 @@
1
+ module MongoidFilter
2
+ class FormObject
3
+ extend ActiveModel::Naming
4
+
5
+ attr_reader :form_fields_struct
6
+
7
+ def self.model_name
8
+ ActiveModel::Name.new(self)
9
+ end
10
+
11
+ def initialize(filter_params)
12
+ @form_fields_struct = OpenStruct.new(filter_params)
13
+ end
14
+
15
+ def method_missing(method, *args, &block)
16
+ @form_fields_struct.public_send(method, *args, &block)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module MongoidFilter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mongoid_filter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mongoid_filter"
8
+ spec.version = MongoidFilter::VERSION
9
+ spec.authors = ["Egor Lynko"]
10
+ spec.email = ["flexoid@gmail.com"]
11
+ spec.description = %q{Provides methods to filter collections by form parameters}
12
+ spec.summary = %q{Helper methods to filter collections}
13
+ spec.homepage = "https://github.com/flexoid/mongoid-filter"
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_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency('rspec')
24
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ describe MongoidFilter do
4
+ let(:movie_klass) do
5
+ Class.new do
6
+ include Mongoid::Document
7
+ include MongoidFilter
8
+ end
9
+ end
10
+
11
+ let(:movie) { movie_klass.new }
12
+ let(:filter_params) { {year_gte: 2005, director_eq: 'Christopher Nolan'} }
13
+
14
+ describe "accessors" do
15
+ it "exist in class" do
16
+ expect(movie_klass.respond_to?(:filter_fields)).to be_true
17
+ expect(movie_klass.respond_to?(:filter_fields=)).to be_true
18
+ expect(movie_klass.respond_to?(:special_filters)).to be_true
19
+ expect(movie_klass.respond_to?(:special_filters=)).to be_true
20
+ expect(movie_klass.respond_to?(:filter_field_aliases)).to be_true
21
+ expect(movie_klass.respond_to?(:filter_field_aliases=)).to be_true
22
+ end
23
+ end
24
+
25
+ describe "#can_filter_by" do
26
+ before(:each) do
27
+ movie_klass.instance_eval do
28
+ can_filter_by :director, :rating, :premiere_date
29
+ can_filter_by :year
30
+ end
31
+ end
32
+
33
+ it "adds fields to class_attribute" do
34
+ expect(movie_klass.filter_fields).to eq([:director, :rating, :premiere_date, :year])
35
+ end
36
+ end
37
+
38
+ describe "#special_filter" do
39
+ @special_filter_proc = ->(date) { Date.strptime(date, '%m/%d/%Y') }
40
+ before(:each) do
41
+ movie_klass.instance_eval do
42
+ special_filter :premiere_date, @special_filter_proc, field_name: :release_date
43
+ end
44
+ end
45
+
46
+ it "adds filter proc to spefial filters" do
47
+ expect(movie_klass.special_filters).to eq({premiere_date: @special_filter_proc})
48
+ end
49
+
50
+ it "adds field alias to aliases hash" do
51
+ expect(movie_klass.filter_field_aliases).to eq({premiere_date: :release_date})
52
+ end
53
+ end
54
+
55
+ describe "#filter_form_object" do
56
+ context "returns empty form object" do
57
+ let(:empty_ostruct) { OpenStruct.new }
58
+
59
+ it "when #filter_by isn't applied to model" do
60
+ form_object = movie_klass.filter_form_object
61
+ expect(form_object.form_fields_struct).to eq(empty_ostruct)
62
+ end
63
+
64
+ it "when #filter_by invoked on model with empty hash" do
65
+ form_object = movie_klass.filter_by({}).filter_form_object
66
+ expect(form_object.form_fields_struct).to eq(empty_ostruct)
67
+ end
68
+
69
+ it "when #filter_by invoked on criteria with empty hash" do
70
+ form_object = movie_klass.where(year: 2013).filter_by({}).filter_form_object
71
+ expect(form_object.form_fields_struct).to eq(empty_ostruct)
72
+ end
73
+ end
74
+
75
+ context "returns correct form object" do
76
+ let(:ostruct) { MongoidFilter::FormObject.new(filter_params).form_fields_struct }
77
+
78
+ it "when #filter_by invoked on model with params" do
79
+ form_object = movie_klass.filter_by(filter_params).filter_form_object
80
+ expect(form_object.form_fields_struct).to eq(ostruct)
81
+ end
82
+
83
+ it "when #filter_by invoked on criteria with params" do
84
+ form_object = movie_klass.where(year: 2013).
85
+ filter_by(filter_params).filter_form_object
86
+ expect(form_object.form_fields_struct).to eq(ostruct)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "#filter_by" do
92
+ before(:each) do
93
+ movie_klass.instance_eval do
94
+ can_filter_by :director, :year, :premiere_date, :score
95
+ special_filter :score,
96
+ ->(score) { score * 10 },
97
+ field_name: :rating
98
+ end
99
+ end
100
+
101
+ describe "operator" do
102
+ %w(gt gte lt lte).each do |operator|
103
+ it operator do
104
+ expect(movie_klass.filter_by({:"year_#{operator}" => 2000}).selector).
105
+ to eq({'year' => {"$#{operator}" => 2000}})
106
+ end
107
+ end
108
+
109
+ it "from" do
110
+ expect(movie_klass.filter_by({year_from: 2000}).selector).
111
+ to eq({'year' => {'$gte' => 2000}})
112
+ end
113
+
114
+ it "to" do
115
+ expect(movie_klass.filter_by({year_to: 2000}).selector).
116
+ to eq({'year' => {'$lte' => 2000}})
117
+ end
118
+
119
+ it "in" do
120
+ expect(movie_klass.filter_by({year_in: [2000, 2001]}).selector).
121
+ to eq({'year' => {'$in' => [2000, 2001]}})
122
+ end
123
+
124
+ it "cont" do
125
+ expect(movie_klass.filter_by({director_cont: 'Nolan'}).selector).
126
+ to eq({'director' => /.*Nolan.*/i})
127
+ end
128
+
129
+ it "combination of operators" do
130
+ expect(movie_klass.filter_by(filter_params).selector).
131
+ to eq({'year' => {'$gte' => 2005}, 'director' => 'Christopher Nolan'})
132
+ end
133
+
134
+ it "unsupported operator" do
135
+ expect(movie_klass.filter_by({year_greater: 2000}).selector).to eq({})
136
+ end
137
+ end
138
+
139
+ describe 'special filter' do
140
+ it 'with custom field name' do
141
+ expect(movie_klass.filter_by({score_gt: 10}).selector).
142
+ to eq({'rating' => {'$gt' => 100}})
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "FormObject" do
148
+ subject { MongoidFilter::FormObject.new(filter_params) }
149
+ it "forwards getting values to inner OStruct" do
150
+ expect(subject.year_gte).to eq(2005)
151
+ expect(subject.director_eq).to eq('Christopher Nolan')
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require 'mongoid'
5
+ require 'mongoid_filter'
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid_filter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Egor Lynko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Provides methods to filter collections by form parameters
63
+ email:
64
+ - flexoid@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - lib/mongoid_filter.rb
75
+ - lib/mongoid_filter/form_object.rb
76
+ - lib/mongoid_filter/version.rb
77
+ - mongoid_filter.gemspec
78
+ - spec/mongoid_filter/mongoid_filter_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: https://github.com/flexoid/mongoid-filter
81
+ licenses:
82
+ - MIT
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.23
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Helper methods to filter collections
105
+ test_files:
106
+ - spec/mongoid_filter/mongoid_filter_spec.rb
107
+ - spec/spec_helper.rb