trailblazer-finder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +45 -0
- data/.rubocop_todo.yml +52 -0
- data/.travis.yml +15 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +23 -0
- data/README.md +494 -0
- data/Rakefile +29 -0
- data/lib/trailblazer/finder/adapters/active_record/paging.rb +20 -0
- data/lib/trailblazer/finder/adapters/active_record/sorting.rb +20 -0
- data/lib/trailblazer/finder/adapters/active_record.rb +30 -0
- data/lib/trailblazer/finder/adapters/data_mapper/paging.rb +20 -0
- data/lib/trailblazer/finder/adapters/data_mapper/sorting.rb +25 -0
- data/lib/trailblazer/finder/adapters/data_mapper.rb +30 -0
- data/lib/trailblazer/finder/adapters/friendly_id.rb +33 -0
- data/lib/trailblazer/finder/adapters/kaminari.rb +18 -0
- data/lib/trailblazer/finder/adapters/sequel/paging.rb +20 -0
- data/lib/trailblazer/finder/adapters/sequel/sorting.rb +25 -0
- data/lib/trailblazer/finder/adapters/sequel.rb +30 -0
- data/lib/trailblazer/finder/adapters/will_paginate.rb +18 -0
- data/lib/trailblazer/finder/adapters.rb +26 -0
- data/lib/trailblazer/finder/base.rb +98 -0
- data/lib/trailblazer/finder/errors/block_ignored.rb +11 -0
- data/lib/trailblazer/finder/errors/invalid_defined_by_value.rb +11 -0
- data/lib/trailblazer/finder/errors/invalid_number.rb +16 -0
- data/lib/trailblazer/finder/errors/missing_entity_type.rb +11 -0
- data/lib/trailblazer/finder/errors/with_ignored.rb +11 -0
- data/lib/trailblazer/finder/features/paging.rb +55 -0
- data/lib/trailblazer/finder/features/sorting.rb +66 -0
- data/lib/trailblazer/finder/features.rb +22 -0
- data/lib/trailblazer/finder/filter.rb +66 -0
- data/lib/trailblazer/finder/find.rb +29 -0
- data/lib/trailblazer/finder/utils/extra.rb +31 -0
- data/lib/trailblazer/finder/utils/params.rb +28 -0
- data/lib/trailblazer/finder/utils/parse.rb +25 -0
- data/lib/trailblazer/finder/utils/string.rb +35 -0
- data/lib/trailblazer/finder/version.rb +5 -0
- data/lib/trailblazer/finder.rb +29 -0
- data/lib/trailblazer/operation/finder.rb +61 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/spec_helper_active_record.rb +50 -0
- data/spec/spec_helper_data_mapper.rb +35 -0
- data/spec/spec_helper_sequel.rb +32 -0
- data/spec/support/paging_shared_example.rb +65 -0
- data/spec/support/sorting_shared_example.rb +95 -0
- data/spec/trailblazer/finder/adapters/active_record/base_spec.rb +112 -0
- data/spec/trailblazer/finder/adapters/active_record/paging_spec.rb +64 -0
- data/spec/trailblazer/finder/adapters/active_record/sorting_spec.rb +82 -0
- data/spec/trailblazer/finder/adapters/data_mapper/base_spec.rb +112 -0
- data/spec/trailblazer/finder/adapters/data_mapper/paging_spec.rb +64 -0
- data/spec/trailblazer/finder/adapters/data_mapper/sorting_spec.rb +85 -0
- data/spec/trailblazer/finder/adapters/friendly_id_spec.rb +46 -0
- data/spec/trailblazer/finder/adapters/kaminari_spec.rb +64 -0
- data/spec/trailblazer/finder/adapters/sequel/base_spec.rb +112 -0
- data/spec/trailblazer/finder/adapters/sequel/paging_spec.rb +64 -0
- data/spec/trailblazer/finder/adapters/sequel/sorting_spec.rb +82 -0
- data/spec/trailblazer/finder/adapters/will_paginate_spec.rb +71 -0
- data/spec/trailblazer/finder/adapters_spec.rb +110 -0
- data/spec/trailblazer/finder/base_spec.rb +329 -0
- data/spec/trailblazer/finder/features/paging_spec.rb +104 -0
- data/spec/trailblazer/finder/features/sorting_spec.rb +100 -0
- data/spec/trailblazer/finder/features_spec.rb +55 -0
- data/spec/trailblazer/finder/filter_spec.rb +133 -0
- data/spec/trailblazer/finder/find_spec.rb +72 -0
- data/spec/trailblazer/finder/utils/extra_spec.rb +41 -0
- data/spec/trailblazer/finder/utils/params_spec.rb +39 -0
- data/spec/trailblazer/finder/utils/parse_spec.rb +33 -0
- data/spec/trailblazer/finder/utils/string_spec.rb +25 -0
- data/spec/trailblazer/operation/finder_spec.rb +103 -0
- data/spec/trailblazer/operation/paging_spec.rb +68 -0
- data/spec/trailblazer/operation/sorting_spec.rb +80 -0
- data/trailblazer-finder.gemspec +41 -0
- metadata +402 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Finder
|
3
|
+
module Filter
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def filter_by(name, options = nil, &block)
|
10
|
+
return super unless options.is_a?(Hash) && options[:defined_by]
|
11
|
+
|
12
|
+
raise Error::BlockIgnored if block
|
13
|
+
raise Error::WithIgnored if options[:with]
|
14
|
+
|
15
|
+
handler = Handler.build(name, options[:defined_by])
|
16
|
+
|
17
|
+
super(name, options, &handler)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Handler
|
22
|
+
module_function
|
23
|
+
|
24
|
+
def build(name, defined_bys) # rubocop:disable Metrics/MethodLength
|
25
|
+
defined_bys = defined_bys.map(&:to_s)
|
26
|
+
handler = self
|
27
|
+
lambda do |entity_type, value|
|
28
|
+
handler.apply_filter(
|
29
|
+
object: self,
|
30
|
+
filter_by: name,
|
31
|
+
defined_bys: defined_bys,
|
32
|
+
entity_type: entity_type,
|
33
|
+
value: value
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def apply_filter(object:, filter_by:, defined_bys:, entity_type:, value:) # rubocop:disable Metrics/MethodLength
|
39
|
+
return if value.nil? || value == ''
|
40
|
+
|
41
|
+
unless defined_bys.include? value
|
42
|
+
return handle_invalid_value(
|
43
|
+
object: object,
|
44
|
+
filter_by: filter_by,
|
45
|
+
defined_bys: defined_bys,
|
46
|
+
entity_type: entity_type,
|
47
|
+
value: value
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
object.send("apply_#{filter_by}_with_#{Utils::String.underscore(value)}", entity_type)
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_invalid_value(object:, filter_by:, defined_bys:, entity_type:, value:)
|
55
|
+
specific = "handle_invalid_#{filter_by}"
|
56
|
+
return object.send(specific, entity_type, value) if object.respond_to? specific, true
|
57
|
+
|
58
|
+
catch_all = 'handle_invalid_defined_by'
|
59
|
+
return object.send(catch_all, filter_by, entity_type, value) if object.respond_to? catch_all, true
|
60
|
+
|
61
|
+
raise Error::InvalidDefinedByValue.new(filter_by, defined_bys, value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Finder
|
3
|
+
class Find
|
4
|
+
attr_reader :params
|
5
|
+
|
6
|
+
def initialize(entity_type, params, actions)
|
7
|
+
@entity_type = entity_type
|
8
|
+
@actions = actions
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def param(name)
|
13
|
+
@params[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(context)
|
17
|
+
@params.inject(@entity_type) do |entity_type, (name, value)|
|
18
|
+
value = Utils::Parse.date(value) if Utils::Parse.date(value)
|
19
|
+
new_entity_type = context.instance_exec entity_type, value, &@actions[name]
|
20
|
+
new_entity_type || entity_type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def count(context)
|
25
|
+
query(context).count
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Finder
|
3
|
+
# Helper module
|
4
|
+
module Utils
|
5
|
+
class Extra
|
6
|
+
def self.ensure_included(item, collection)
|
7
|
+
if collection.include? item
|
8
|
+
item
|
9
|
+
else
|
10
|
+
collection.first
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.deep_copy(object) # rubocop:disable Metrics/MethodLength
|
15
|
+
case object
|
16
|
+
when Array
|
17
|
+
object.map { |element| deep_copy(element) }
|
18
|
+
when Hash
|
19
|
+
object.each_with_object({}) do |(key, value), result|
|
20
|
+
result[key] = deep_copy(value)
|
21
|
+
end
|
22
|
+
when NilClass, FalseClass, TrueClass, Symbol, Method, Numeric
|
23
|
+
object
|
24
|
+
else
|
25
|
+
object.dup
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# TODO: Clean this shit up
|
2
|
+
module Trailblazer
|
3
|
+
class Finder
|
4
|
+
# Helper module
|
5
|
+
module Utils
|
6
|
+
module Params
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def stringify_keys(hash)
|
10
|
+
hash = hash.to_unsafe_h if hash.respond_to? :to_unsafe_h
|
11
|
+
Hash[(hash || {}).map { |k, v| [k.to_s, v] }]
|
12
|
+
end
|
13
|
+
|
14
|
+
def slice_keys(hash, keys)
|
15
|
+
keys.each_with_object({}) do |key, memo|
|
16
|
+
memo[key] = hash[key] if hash.key? key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalize_params(defaults, filters, keys)
|
21
|
+
(defaults || {}).merge(
|
22
|
+
slice_keys(stringify_keys(filters || {}), keys || [])
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
class Finder
|
5
|
+
# Helper module
|
6
|
+
module Utils
|
7
|
+
class Parse
|
8
|
+
# Need a replacement for this
|
9
|
+
def self.date(value)
|
10
|
+
return if value.nil?
|
11
|
+
return if [true, false].include? value
|
12
|
+
return if value.is_a? Integer
|
13
|
+
return if value =~ /[[:alpha:]]/
|
14
|
+
Date.parse(value).strftime('%Y-%m-%d')
|
15
|
+
rescue ArgumentError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.term(value)
|
20
|
+
"%#{value.gsub(/\s+/, '%')}%"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
class Finder
|
5
|
+
# Helper module
|
6
|
+
module Utils
|
7
|
+
class String
|
8
|
+
def self.blank?(value)
|
9
|
+
return false if num?(value)
|
10
|
+
value.nil? || value.strip.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.num?(str)
|
14
|
+
Integer(str)
|
15
|
+
rescue ArgumentError, TypeError
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.camelize(text)
|
20
|
+
text.to_s.gsub(/(?:^|_)(.)/) { Regexp.last_match[1].upcase }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.underscore(text)
|
24
|
+
text
|
25
|
+
.to_s.gsub(/::/, '/')
|
26
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
27
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
28
|
+
.tr('-', '_')
|
29
|
+
.tr(' ', '_')
|
30
|
+
.downcase
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'trailblazer/finder/version'
|
2
|
+
require 'trailblazer/finder/errors/block_ignored'
|
3
|
+
require 'trailblazer/finder/errors/invalid_defined_by_value'
|
4
|
+
require 'trailblazer/finder/errors/invalid_number'
|
5
|
+
require 'trailblazer/finder/errors/missing_entity_type'
|
6
|
+
require 'trailblazer/finder/errors/with_ignored'
|
7
|
+
require 'trailblazer/finder'
|
8
|
+
require 'trailblazer/finder/utils/params'
|
9
|
+
require 'trailblazer/finder/utils/parse'
|
10
|
+
require 'trailblazer/finder/utils/string'
|
11
|
+
require 'trailblazer/finder/utils/extra'
|
12
|
+
require 'trailblazer/finder/base'
|
13
|
+
require 'trailblazer/finder/find'
|
14
|
+
require 'trailblazer/finder/filter'
|
15
|
+
require 'trailblazer/finder/features'
|
16
|
+
require 'trailblazer/finder/adapters'
|
17
|
+
|
18
|
+
# :nocov:
|
19
|
+
require 'trailblazer/operation/finder' if Gem.loaded_specs.key?('trailblazer')
|
20
|
+
# :nocov:
|
21
|
+
|
22
|
+
module Trailblazer
|
23
|
+
class Finder
|
24
|
+
include Base
|
25
|
+
include Filter
|
26
|
+
include Features
|
27
|
+
include Adapters
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
# Really gotta clean this up, but can't be bothered right now
|
3
|
+
module Trailblazer
|
4
|
+
class Operation
|
5
|
+
def self.Finder(finder_class, action = nil, entity_type = nil)
|
6
|
+
task = Trailblazer::Activity::TaskBuilder::Binary.call(Finder.new)
|
7
|
+
|
8
|
+
extension = Trailblazer::Activity::TaskWrap::Merge.new(
|
9
|
+
Wrap::Inject::Defaults(
|
10
|
+
'finder.class' => finder_class,
|
11
|
+
'finder.entity_type' => entity_type,
|
12
|
+
'finder.action' => action
|
13
|
+
)
|
14
|
+
)
|
15
|
+
|
16
|
+
{ task: task, id: 'finder.build', extension: [extension] }
|
17
|
+
end
|
18
|
+
|
19
|
+
class Finder
|
20
|
+
def call(options, params:, **)
|
21
|
+
builder = Finder::Builder.new
|
22
|
+
options[:finder] = finder = builder.call(options, params)
|
23
|
+
options['result.finder'] = result = Result.new(!finder.nil?, {})
|
24
|
+
|
25
|
+
result.success?
|
26
|
+
end
|
27
|
+
|
28
|
+
class Builder
|
29
|
+
def call(options, params)
|
30
|
+
finder_class = options['finder.class']
|
31
|
+
entity_type = options['finder.entity_type'] || nil
|
32
|
+
action = options['finder.action'] || :all
|
33
|
+
action = :all unless %i[all single].include?(action)
|
34
|
+
|
35
|
+
send("#{action}!", finder_class, entity_type, params, options['finder.action'])
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def all!(finder_class, entity_type, params, *)
|
41
|
+
finder_class.new(entity_type: entity_type, filter: params[:f], page: params[:page], per_page: params[:per_page])
|
42
|
+
end
|
43
|
+
|
44
|
+
def single!(finder_class, entity_type, params, *)
|
45
|
+
apply_id(params)
|
46
|
+
if entity_type.nil?
|
47
|
+
finder_class.new(filter: params[:f], page: params[:page], per_page: params[:per_page]).results.first
|
48
|
+
else
|
49
|
+
finder_class.new(entity_type: entity_type, filter: params[:f]).results.first
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_id(params)
|
54
|
+
return if params[:id].nil?
|
55
|
+
params[:f] = {} unless params.key?('f')
|
56
|
+
params[:f][:id] = params[:id] unless params[:f].key?('id')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
add_group 'Trailblazer-Finder', 'lib'
|
5
|
+
add_group 'Tests', 'spec'
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'coveralls'
|
13
|
+
Coveralls.wear!
|
14
|
+
|
15
|
+
require 'trailblazer/finder'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/paging_shared_example'
|
3
|
+
require_relative 'support/sorting_shared_example'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
7
|
+
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
self.verbose = false
|
10
|
+
|
11
|
+
create_table :products, force: true do |t|
|
12
|
+
t.string :name
|
13
|
+
t.string :slug
|
14
|
+
t.integer :category_id
|
15
|
+
t.integer :price
|
16
|
+
|
17
|
+
t.timestamps null: true
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :categories, force: true do |t|
|
21
|
+
t.string :title
|
22
|
+
t.string :slug
|
23
|
+
|
24
|
+
t.timestamps null: true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Product < ActiveRecord::Base
|
29
|
+
belongs_to :category
|
30
|
+
end
|
31
|
+
|
32
|
+
class Category < ActiveRecord::Base
|
33
|
+
end
|
34
|
+
|
35
|
+
module ActiveRecord
|
36
|
+
class Base
|
37
|
+
def self.reset_pk_sequence
|
38
|
+
case ActiveRecord::Base.connection.adapter_name
|
39
|
+
when 'SQLite'
|
40
|
+
new_max = maximum(primary_key) || 0
|
41
|
+
update_seq_sql = "update sqlite_sequence set seq = #{new_max} where name = '#{table_name}';"
|
42
|
+
ActiveRecord::Base.connection.execute(update_seq_sql)
|
43
|
+
when 'PostgreSQL'
|
44
|
+
ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
|
45
|
+
else
|
46
|
+
raise 'Task not implemented for this DB adapter'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/paging_shared_example'
|
3
|
+
require 'data_mapper'
|
4
|
+
|
5
|
+
DataMapper.setup(:default, 'sqlite::memory:')
|
6
|
+
|
7
|
+
class DProduct
|
8
|
+
include DataMapper::Resource
|
9
|
+
|
10
|
+
property :id, Serial
|
11
|
+
property :name, String
|
12
|
+
property :slug, String
|
13
|
+
property :d_category_id, Integer
|
14
|
+
property :price, Integer
|
15
|
+
property :created_at, DateTime
|
16
|
+
property :updated_at, DateTime
|
17
|
+
|
18
|
+
belongs_to :d_category
|
19
|
+
end
|
20
|
+
|
21
|
+
class DCategory
|
22
|
+
include DataMapper::Resource
|
23
|
+
|
24
|
+
property :id, Serial
|
25
|
+
property :title, String
|
26
|
+
property :slug, String
|
27
|
+
property :created_at, DateTime
|
28
|
+
property :updated_at, DateTime
|
29
|
+
|
30
|
+
has n, :d_product
|
31
|
+
end
|
32
|
+
|
33
|
+
DataMapper.finalize
|
34
|
+
require 'dm-migrations'
|
35
|
+
DataMapper.auto_migrate!
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/paging_shared_example'
|
3
|
+
require_relative 'support/sorting_shared_example'
|
4
|
+
require 'sequel'
|
5
|
+
Sequel::Model.plugin :timestamps
|
6
|
+
DB = Sequel.sqlite
|
7
|
+
|
8
|
+
DB.create_table :s_products do
|
9
|
+
primary_key :id
|
10
|
+
String :name
|
11
|
+
String :slug
|
12
|
+
Integer :s_category_id
|
13
|
+
Integer :price
|
14
|
+
DateTime :created_at
|
15
|
+
DateTime :updated_at
|
16
|
+
end
|
17
|
+
|
18
|
+
DB.create_table :s_categories do
|
19
|
+
primary_key :id
|
20
|
+
String :title
|
21
|
+
String :slug
|
22
|
+
DateTime :created_at
|
23
|
+
DateTime :updated_at
|
24
|
+
end
|
25
|
+
|
26
|
+
class SProduct < Sequel::Model
|
27
|
+
many_to_one :s_category
|
28
|
+
end
|
29
|
+
|
30
|
+
class SCategory < Sequel::Model
|
31
|
+
one_to_many :s_product
|
32
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
shared_examples_for 'a paging feature' do
|
2
|
+
describe '#page' do
|
3
|
+
it 'treats nil page as 0' do
|
4
|
+
finder = finder_with_page nil
|
5
|
+
expect(finder.page).to eq 0
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'treats negative page numbers as positive' do
|
9
|
+
finder = finder_with_page(-1)
|
10
|
+
expect(finder.page).to eq 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#per_page' do
|
15
|
+
it 'returns the class defined per page' do
|
16
|
+
finder = finder_class.new
|
17
|
+
expect(finder.per_page).to eq 2
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can be overwritten as option' do
|
21
|
+
finder = finder_class.new per_page: 3
|
22
|
+
expect(finder.per_page).to eq 3
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'respects min per page' do
|
26
|
+
finder = finder_class.new per_page: 1
|
27
|
+
expect(finder.per_page).to eq 2
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'respects max per page' do
|
31
|
+
finder = finder_class.new per_page: 100
|
32
|
+
expect(finder.per_page).to eq 10
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '.per_page' do
|
37
|
+
it 'does not accept 0' do
|
38
|
+
expect { define_finder_class { per_page(0) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'does not accept negative number' do
|
42
|
+
expect { define_finder_class { per_page(-1) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.min_per_page' do
|
47
|
+
it 'does not accept 0' do
|
48
|
+
expect { define_finder_class { min_per_page(0) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not accept negative number' do
|
52
|
+
expect { define_finder_class { min_per_page(-1) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '.max_per_page' do
|
57
|
+
it 'does not accept 0' do
|
58
|
+
expect { define_finder_class { max_per_page(0) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'does not accept negative number' do
|
62
|
+
expect { define_finder_class { max_per_page(-1) } }.to raise_error Trailblazer::Finder::Error::InvalidNumber
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
shared_examples_for 'a sorting feature' do
|
2
|
+
describe '#sort?' do
|
3
|
+
it 'matches the sort option' do
|
4
|
+
finder = finder_with_sort 'price desc'
|
5
|
+
|
6
|
+
expect(finder).to be_sort :price
|
7
|
+
expect(finder).not_to be_sort :name
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'matches string also' do
|
11
|
+
finder = finder_with_sort 'price desc'
|
12
|
+
|
13
|
+
expect(finder).to be_sort 'price'
|
14
|
+
expect(finder).not_to be_sort 'name'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'matches exact strings' do
|
18
|
+
finder = finder_with_sort 'price desc'
|
19
|
+
|
20
|
+
expect(finder).to be_sort 'price desc'
|
21
|
+
expect(finder).not_to be_sort 'price asc'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#sort_attribute' do
|
26
|
+
it 'returns sort option attribute' do
|
27
|
+
finder = finder_with_sort 'price desc'
|
28
|
+
expect(finder.sort_attribute).to eq 'price'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'defaults to the first sort by option' do
|
32
|
+
finder = finder_with_sort
|
33
|
+
expect(finder.sort_attribute).to eq 'name'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'rejects invalid sort options, uses defaults' do
|
37
|
+
finder = finder_with_sort 'invalid'
|
38
|
+
expect(finder.sort_attribute).to eq 'name'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#sort_direction' do
|
43
|
+
it 'returns asc or desc' do
|
44
|
+
expect(finder_with_sort('price desc').sort_direction).to eq 'desc'
|
45
|
+
expect(finder_with_sort('price asc').sort_direction).to eq 'asc'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'defaults to desc' do
|
49
|
+
expect(finder_with_sort.sort_direction).to eq 'desc'
|
50
|
+
expect(finder_with_sort('price').sort_direction).to eq 'desc'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'rejects invalid sort options, uses desc' do
|
54
|
+
expect(finder_with_sort('price foo').sort_direction).to eq 'desc'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#sort_direction_for' do
|
59
|
+
it 'returns desc if current sort attribute is not the given attribute' do
|
60
|
+
expect(finder_with_sort('price desc').sort_direction_for('name')).to eq 'desc'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns asc if current sort attribute is the given attribute' do
|
64
|
+
expect(finder_with_sort('name desc').sort_direction_for('name')).to eq 'asc'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns desc if current sort attribute is the given attribute, but asc with direction' do
|
68
|
+
expect(finder_with_sort('name asc').sort_direction_for('name')).to eq 'desc'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#sort_params_for' do
|
73
|
+
it 'adds sort direction' do
|
74
|
+
finder = finder_with_sort 'name', name: 'test'
|
75
|
+
expect(finder.sort_params_for(:price)).to eq 'sort' => 'price desc', 'name' => 'test'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'reverses sort direction if this is the current sort attribute' do
|
79
|
+
finder = finder_with_sort 'name desc', name: 'test'
|
80
|
+
expect(finder.sort_params_for(:name)).to eq 'sort' => 'name asc', 'name' => 'test'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'accepts additional options' do
|
84
|
+
finder = finder_with_sort
|
85
|
+
expect(finder.sort_params_for(:price, name: 'value')).to eq 'sort' => 'price desc', 'name' => 'value'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#reverted_sort_direction' do
|
90
|
+
it 'reverts sorting direction' do
|
91
|
+
expect(finder_with_sort('price desc').reverted_sort_direction).to eq 'asc'
|
92
|
+
expect(finder_with_sort('price asc').reverted_sort_direction).to eq 'desc'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|