Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
0469f5d
Remove the default from the AttributeInstruction#operator method
fractaledmind Jun 29, 2019
dd04414
Move the filtering strategies into directory/module namespaces
fractaledmind Jun 29, 2019
5c277af
Add the base predicate constants for filtering
fractaledmind Jun 29, 2019
4eeff53
Add the strategy-specific operator resolution logic
fractaledmind Jun 29, 2019
eac3288
Implement filtering-specific SetInstruction classes
fractaledmind Jun 29, 2019
6df19a2
Change the default spec running behavior
fractaledmind Jun 29, 2019
55144bc
Update the filtering specs
fractaledmind Jun 29, 2019
006b312
Move the filtering strategies into directory/module namespaces
fractaledmind Jun 29, 2019
e22fc21
Begin separating global set_instructions from operation-specific set-…
fractaledmind Jun 29, 2019
6e53b48
Fix enumerable intersection filtering when working across associations
fractaledmind Jun 29, 2019
4dd26f3
Change the default spec running behavior
fractaledmind Jun 29, 2019
e6a4fbc
Fix the generic factory date sequence to never output an invalid date…
fractaledmind Jun 29, 2019
675ed60
Update the Rakefile to have TravisCI run the full spec setup
fractaledmind Jun 29, 2019
c986e05
Merge pull request #41 from fractaledmind/fix/enumerable-intersection…
fractaledmind Jun 29, 2019
fc790a0
Merge pull request #42 from fractaledmind/feature/test-setup
fractaledmind Jun 29, 2019
ddf9a58
Fix rubocop issues
fractaledmind Jun 29, 2019
e4ae19b
Merge pull request #43 from fractaledmind/fix/rubocop
fractaledmind Jun 29, 2019
7874c8e
Merge branch 'master' into feature/operation-set-instructions
fractaledmind Jun 29, 2019
7c92767
Get the Enumerable filtering strategy working with the new SetInstruc…
fractaledmind Jun 29, 2019
f5de6bf
Move filtering specific SetInstruction from the top-level class into …
fractaledmind Jun 29, 2019
5580fc8
Merge pull request #44 from fractaledmind/feature/operation-set-instr…
fractaledmind Jun 29, 2019
7454e63
Merge branch 'master' into feature/base-predicates
fractaledmind Jun 29, 2019
7075da3
Add the various 'reducer' predicates
fractaledmind Jun 30, 2019
b6a9abb
Add the various 'reducer' predicates to the 2 strategy operators
fractaledmind Jun 30, 2019
4c22c19
Update the various SetInstruction classes to work with all of the pre…
fractaledmind Jun 30, 2019
27c7bce
Update the filtering predicates spec to properly test inclusion opera…
fractaledmind Jun 30, 2019
c4517e9
Rubocop fixes
fractaledmind Jun 30, 2019
3b6c515
Get the specs for inconclusive predicates working
fractaledmind Jun 30, 2019
7453cd4
Don't cover codepaths we can't reach
fractaledmind Jun 30, 2019
287018b
Add tests for the type caster
fractaledmind Jun 30, 2019
2dba637
Fix the filtering predicates request spec
fractaledmind Jun 30, 2019
e4f76d9
Merge pull request #47 from fractaledmind/fix/spec-coverage
fractaledmind Jun 30, 2019
9c03e51
Merge branch 'master' into feature/base-predicates
fractaledmind Jun 30, 2019
e14d96b
Use the operator constants in both predicate specs
fractaledmind Jun 30, 2019
9818bd2
Merge branch 'feature/base-predicates' into feature/other-predicates
fractaledmind Jun 30, 2019
064e60c
Get the filtering specs in full working order
fractaledmind Jun 30, 2019
c2662c8
Add is_true, is_false, is_null, not_null, is_present, is_blank operators
fractaledmind Jun 30, 2019
90615c1
Test the is_true and is_false predicates properly
fractaledmind Jun 30, 2019
1f33b2b
Make the filtering predicate spec handle the varous IN predicates pro…
fractaledmind Jul 9, 2019
aa22a6c
Add computed matcher predicates (start, end, contain)
fractaledmind Jul 9, 2019
79f127f
Update the ActiveRecord strategy organization to clean up handling th…
fractaledmind Jul 9, 2019
dab5c6f
Simplify the float and decimal factory generators
fractaledmind Jul 9, 2019
cf9a67d
Use the built in Ruby start_with?, end_with?, and include? string met…
fractaledmind Jul 11, 2019
9885dfc
Allow sort params to be passed in short or long form
fractaledmind Jul 31, 2019
3920a4f
Add view helpers for the range of records shown on the current page
fractaledmind Jul 31, 2019
3514e8a
Merge pull request #45 from fractaledmind/feature/base-predicates
fractaledmind Jul 31, 2019
63eba7a
Merge pull request #46 from fractaledmind/feature/other-predicates
fractaledmind Jul 31, 2019
08c7424
Merge pull request #48 from fractaledmind/feature/computed-unary-pred…
fractaledmind Jul 31, 2019
ac392da
Merge pull request #49 from fractaledmind/feature/computed-matches-pr…
fractaledmind Jul 31, 2019
a0f9cb4
Merge branch 'master' into feature/sort-instructions
fractaledmind Jul 31, 2019
be20e14
Merge branch 'master' into feature/pagination-helpers
fractaledmind Jul 31, 2019
b5f9742
bundle exec rubocop --safe-auto-correct .
fractaledmind Jul 31, 2019
76987cf
Merge pull request #50 from fractaledmind/feature/sort-instructions
fractaledmind Jul 31, 2019
0e5989b
Merge pull request #51 from fractaledmind/feature/pagination-helpers
fractaledmind Jul 31, 2019
09e27b8
Merge pull request #52 from fractaledmind/fix/rubocop-fixes
fractaledmind Jul 31, 2019
0db1eaa
use AR's data dictionary to look up database column types or allow ap…
fractaledmind Jul 31, 2019
7b709b9
Add specs (with some fixes) for the new pagination record helpers
fractaledmind Aug 2, 2019
4e94261
Fix the sort request spec to actually test something
fractaledmind Aug 2, 2019
34f7bee
Merge pull request #53 from fractaledmind/feature/improve-coverage
fractaledmind Aug 2, 2019
e5c2763
Merge branch 'master' into feature/improved-type-inferencing
fractaledmind Aug 2, 2019
91ff66f
Merge pull request #54 from fractaledmind/feature/improved-type-infer…
fractaledmind Aug 2, 2019
746be6e
Add tests to cover the new #filter_set_types method for short-circuit…
fractaledmind Aug 2, 2019
a27e65b
Bump version and update CHANGELOG
fractaledmind Aug 2, 2019
840f0a3
Bump version and update CHANGELOG
fractaledmind Aug 2, 2019
14904c9
Merge branch 'master' of github.com:fractaledmind/actionset
fractaledmind Aug 2, 2019
7a03c2a
Bump to version 0.9.1 to ensure a good build
fractaledmind Aug 2, 2019
f9ea022
Allow the sort param to accept form-friendly structures
fractaledmind Aug 6, 2019
7ce176f
Allow the filter params to accept form-friendly structures
fractaledmind Aug 6, 2019
bc2ae8b
Fix some Rubocop issues
fractaledmind Aug 7, 2019
01b0574
Move the logic for transforming sorting and filtering request params …
fractaledmind Aug 7, 2019
86d594c
Merge pull request #55 from fractaledmind/feature/form-friendly-params
fractaledmind Aug 7, 2019
f8f6503
Bump to version 0.9.2 and update CHANGELOG
fractaledmind Aug 7, 2019
3cac0e6
Clean up the filtering and sorting specs to have helpers determine wh…
fractaledmind Aug 9, 2019
645cef8
Ensure that the CI run rake task runs all possible tests
fractaledmind Aug 9, 2019
b2dba36
Merge pull request #57 from fractaledmind/feature/ci-runs-all-tests
fractaledmind Aug 9, 2019
9b877a1
Use a DBMS-agnostic version of NULL sorting
barendt Aug 9, 2019
9e7d9e4
Add `arel_column_name` method to ActiveRecordSetInstruction
Mar 3, 2020
038ff6d
Fix nil_sorter_for method to account for direction.
Mar 3, 2020
1fadd78
Bump ruby version
Mar 3, 2020
2d2ed3e
Merge branch 'master' into feature/null_sorting
emolayi Mar 4, 2020
b424cb3
Refactor sorting active_record_strategy methods
Mar 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.4.1
2.4.9
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sudo: false
language: ruby
rvm:
- 2.4.1
- 2.4.9
before_install:
- gem install bundler -v 1.15.4
- export TZ=America/New_York
Expand Down
58 changes: 58 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
v 0.9.2
- allow for form-friendly filter parameters
```
{
'0': {
attribute: :foo,
operator: :EQ,
query: 'query'
},
'1': {
attribute: :bar,
operator: :GT,
query: 'value'
}
}

{ foo: 'query', bar(GT): 'value' }
```
- allow for form-friendly sort parameters
```
{
'0': {
attribute: :foo,
direction: :desc
},
'1': {
attribute: :bar,
direction: :asc
}
}

{ foo: 'desc', bar: 'asc' }
```

v 0.9.1
- ensure that our RubyGems build is stable, since we had a TravisCI build problem in the last version
v 0.9.0
- Add Enumerable support for the base filtering operations
+ EQ, NOT_EQ, EQ_ANY, EQ_ALL, NOT_EQ_ANY, NOT_EQ_ALL
+ IN, NOT_IN, IN_ANY, IN_ALL, NOT_IN_ANY, NOT_IN_ALL
+ MATCHES, DOES_NOT_MATCH, MATCHES_ANY, MATCHES_ALL, DOES_NOT_MATCH_ANY, DOES_NOT_MATCH_ALL
+ LT, LTEQ, LT_ANY, LT_ALL, LTEQ_ANY, LTEQ_ALL
+ GT, GTEQ, GT_ANY, GT_ALL, GTEQ_ANY, GTEQ_ALL
+ BETWEEN, NOT_BETWEEN
- Add unary predicate filtering operators, for both ActiveRecord and Enumerable
+ IS_TRUE, IS_FALSE
+ IS_NULL, NOT_NULL
+ IS_PRESENT, IS_BLANK
- Add computed matcher filtering operators, for both ActiveRecord and Enumerable
+ MATCH_START, MATCH_START_ANY, MATCH_START_ALL, MATCH_NOT_START, MATCH_NOT_START_ANY, MATCH_NOT_START_ALL
+ MATCH_END, MATCH_END_ANY, MATCH_END_ALL, MATCH_NOT_END, MATCH_NOT_END_ANY, MATCH_NOT_END_ALL
+ MATCH_CONTAIN, MATCH_CONTAIN_ANY, MATCH_CONTAIN_ALL, MATCH_NOT_CONTAIN, MATCH_NOT_CONTAIN_ANY, MATCH_NOT_CONTAIN_ALL
- Add view helpers for the range of records shown on the current page
- Use ActiveRecord's data dictionary to look up database column types when converting filter params to filter instructions
- Allow app to define type hints for filter attributes when converting filter params to filter instructions
- Allow sort params to be passed in short or long form
+ e.g. { attribute: x, direction: x } or { attribute: direction }
- Fix enumerable intersection filtering when working across associations
v 0.8.2
- add `ActiveSet.configuration.on_asc_sort_nils_come` configuration
v 0.8.1
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
actionset (0.8.2)
actionset (0.9.2)
activesupport (>= 4.0.2)
railties

Expand Down
9 changes: 8 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec
task :full_spec do
ENV['COVERAGE'] = 'true'
ENV['INSPECT_FAILURE'] = 'true'
ENV['LOGICALLY_EXHAUSTIVE_REQUEST_SPECS'] = 'true'
Rake::Task['spec'].invoke
end

task default: :full_spec
2 changes: 1 addition & 1 deletion actionset.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |spec|
spec.platform = Gem::Platform::RUBY
spec.name = 'actionset'
spec.version = '0.8.2'
spec.version = '0.9.2'
spec.authors = ['Stephen Margheim']
spec.email = ['stephen.margheim@gmail.com']

Expand Down
31 changes: 8 additions & 23 deletions lib/action_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
require 'active_support/lazy_load_hooks'
require 'active_set'

require_relative './action_set/attribute_value'
require_relative './action_set/filter_instructions'
require_relative './action_set/sort_instructions'
require_relative './action_set/helpers/helper_methods'

module ActionSet
Expand All @@ -24,13 +25,13 @@ def process_set(set)

def filter_set(set)
active_set = ensure_active_set(set)
active_set = active_set.filter(filter_instructions_for(set)) if filter_params.any?
active_set = active_set.filter(FilterInstructions.new(filter_params, set, self).get) if filter_params.any?
active_set
end

def sort_set(set)
active_set = ensure_active_set(set)
active_set = active_set.sort(sort_params) if sort_params.any?
active_set = active_set.sort(SortInstructions.new(sort_params, set, self).get) if sort_params.any?
active_set
end

Expand All @@ -52,30 +53,11 @@ def export_set(set)

private

def filter_instructions_for(set)
flatten_keys_of(filter_params).reject { |_, v| v.try(:empty?) }.each_with_object({}) do |(keypath, value), memo|
typecast_value = if value.respond_to?(:each)
value.map { |v| filter_typecasted_value_for(keypath, v, set) }
else
filter_typecasted_value_for(keypath, value, set)
end

memo[keypath] = typecast_value
end
end

def filter_typecasted_value_for(keypath, value, set)
instruction = ActiveSet::AttributeInstruction.new(keypath, value)
item_with_value = set.find { |i| !instruction.value_for(item: i).nil? }
item_value = instruction.value_for(item: item_with_value)
ActionSet::AttributeValue.new(value)
.cast(to: item_value.class)
end

def paginate_instructions
paginate_params.transform_values(&:to_i)
end

# rubocop:disable Metrics/AbcSize
def export_instructions
{}.tap do |struct|
struct[:format] = export_params[:format] || request.format.symbol
Expand All @@ -93,10 +75,13 @@ def export_instructions
elsif respond_to?(:export_set_columns, true)
send(:export_set_columns)
else
# :nocov:
[{}]
# :nocov:
end
end
end
# rubocop:enable Metrics/AbcSize

def filter_params
params.fetch(:filter, {}).to_unsafe_hash
Expand Down
8 changes: 7 additions & 1 deletion lib/action_set/attribute_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(value)
def cast(to:)
adapters.reduce(nil) do |_, adapter|
mayble_value_or_nil = adapter.new(@raw, to).process
next if mayble_value_or_nil.nil?
next nil if mayble_value_or_nil.nil?

return mayble_value_or_nil
end
Expand Down Expand Up @@ -62,7 +62,9 @@ class ActiveModelAdapter
begin
require 'active_model/type'
rescue LoadError
# :nocov:
require 'active_record/type'
# :nocov:
end

def initialize(raw, target)
Expand Down Expand Up @@ -106,13 +108,17 @@ def can_typecast?(const_name)
def init_typecaster(const_name)
type_class.const_get(const_name).new
rescue StandardError
# :nocov:
nil
# :nocov:
end

def type_class
ActiveModel::Type
rescue NameError
# :nocov:
ActiveRecord::Type
# :nocov:
end
end

Expand Down
72 changes: 72 additions & 0 deletions lib/action_set/filter_instructions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require_relative './attribute_value'

module ActionSet
class FilterInstructions
def initialize(params, set, controller)
@params = params
@set = set
@controller = controller
end

def get
instructions_hash = if form_friendly_complex_params?
form_friendly_complex_params_to_hash
elsif form_friendly_simple_params?
form_friendly_simple_params_to_hash
else
@params
end
flattened_instructions = flatten_keys_of(instructions_hash).reject { |_, v| v.try(:empty?) }
flattened_instructions.each_with_object({}) do |(keypath, value), memo|
memo[keypath] = if value.respond_to?(:map)
value.map { |v| AttributeValue.new(v).cast(to: klass_for_keypath(keypath, v, @set)) }
else
AttributeValue.new(value).cast(to: klass_for_keypath(keypath, value, @set))
end
end
end

private

def form_friendly_complex_params?
@params.key?(:'0')
end

def form_friendly_simple_params?
@params.key?(:attribute) &&
@params.key?(:operator) &&
@params.key?(:query)
end

def form_friendly_complex_params_to_hash
ordered_instructions = @params.sort_by(&:first)
array_of_instructions = ordered_instructions.map { |_, h| ["#{h[:attribute]}(#{h[:operator]})", h[:query]] }
Hash[array_of_instructions]
end

def form_friendly_simple_params_to_hash
{ "#{@params[:attribute]}(#{@params[:operator]})" => @params[:query] }
end

def klass_for_keypath(keypath, value, set)
if @controller.respond_to?(:filter_set_types, true)
type_declarations = @controller.public_send(:filter_set_types)
types = type_declarations['types'] || type_declarations[:types]
klass = types[keypath.join('.')]
return klass if klass
end

if set.is_a?(ActiveRecord::Relation) || set.view.is_a?(ActiveRecord::Relation)
klass_type = set.model.columns_hash.fetch(keypath, nil)&.type
return klass_type.class if klass_type
end

instruction = ActiveSet::AttributeInstruction.new(keypath, value)
item_with_value = set.find { |i| !instruction.value_for(item: i).nil? }
item_value = instruction.value_for(item: item_with_value)
item_value.class
end
end
end
3 changes: 2 additions & 1 deletion lib/action_set/helpers/helper_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require_relative './sort/link_for_helper'
require_relative './pagination/links_for_helper'
require_relative './pagination/path_for_helper'
require_relative './pagination/record_description_for_helper'
require_relative './params/form_for_object_helper'
require_relative './export/path_for_helper'

Expand All @@ -11,6 +11,7 @@ module Helpers
module HelperMethods
include Sort::LinkForHelper
include Pagination::LinksForHelper
include Pagination::RecordDescriptionForHelper
include Params::FormForObjectHelper
include Export::PathForHelper
end
Expand Down
20 changes: 20 additions & 0 deletions lib/action_set/helpers/pagination/record_description_for_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require_relative './record_range_for_helper'
require_relative './record_size_for_helper'

module Pagination
module RecordDescriptionForHelper
include Pagination::RecordRangeForHelper
include Pagination::RecordSizeForHelper

def pagination_record_description_for(set)
[
pagination_record_range_for(set),
'of',
"<strong>#{pagination_record_size_for(set)}</strong>",
'records'
].join('&nbsp;').html_safe
end
end
end
20 changes: 20 additions & 0 deletions lib/action_set/helpers/pagination/record_first_for_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require_relative './current_page_for_helper'
require_relative './page_size_for_helper'

module Pagination
module RecordFirstForHelper
include Pagination::CurrentPageForHelper
include Pagination::PageSizeForHelper

def pagination_record_first_for(set)
current_page = pagination_current_page_for(set)
page_size = pagination_page_size_for(set)

return 1 if current_page == 1

((current_page - 1) * page_size) + 1
end
end
end
26 changes: 26 additions & 0 deletions lib/action_set/helpers/pagination/record_last_for_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

require_relative './current_page_for_helper'
require_relative './record_size_for_helper'
require_relative './page_size_for_helper'
require_relative './total_pages_for_helper'

module Pagination
module RecordLastForHelper
include Pagination::RecordSizeForHelper
include Pagination::CurrentPageForHelper
include Pagination::PageSizeForHelper
include Pagination::TotalPagesForHelper

def pagination_record_last_for(set)
record_size = pagination_record_size_for(set)
current_page = pagination_current_page_for(set)
page_size = pagination_page_size_for(set)
total_pages = pagination_total_pages_for(set)

return record_size if current_page >= total_pages

current_page * page_size
end
end
end
25 changes: 25 additions & 0 deletions lib/action_set/helpers/pagination/record_range_for_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

require_relative './record_first_for_helper'
require_relative './record_last_for_helper'

module Pagination
module RecordRangeForHelper
include Pagination::CurrentPageForHelper
include Pagination::TotalPagesForHelper
include Pagination::RecordFirstForHelper
include Pagination::RecordLastForHelper

def pagination_record_range_for(set)
current_page = pagination_current_page_for(set)
total_pages = pagination_total_pages_for(set)
return 'None' if current_page > total_pages

[
pagination_record_first_for(set),
'&ndash;',
pagination_record_last_for(set)
].join('&nbsp;').html_safe
end
end
end
Loading