trailblazer-finder 0.1.0
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.
- 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
|