mongoid_filter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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