cancancan-squeel 0.1.0 → 0.1.2
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 +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +26 -0
- data/README.md +10 -0
- data/cancancan-squeel.gemspec +1 -0
- data/lib/cancancan/squeel.rb +4 -1
- data/lib/cancancan/squeel/active_record_disabler.rb +8 -7
- data/lib/cancancan/squeel/attribute_mapper.rb +64 -0
- data/lib/cancancan/squeel/expression_builder.rb +64 -0
- data/lib/cancancan/squeel/expression_combinator.rb +80 -0
- data/lib/cancancan/squeel/squeel_adapter.rb +76 -125
- data/lib/cancancan/squeel/version.rb +5 -4
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edbfe73803fc60f8f004514ca962dcb92818cbb4
|
4
|
+
data.tar.gz: fc92e4d84a7557edf512293ebcfefd8995496d82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca8e2455b16e3bc007829341fffdce9682cdebfccc1089e1f0874a6edfcdc35433a955388814dd79b929e79a43e55b0b198fcd959587429d40fba178a4d9fe39
|
7
|
+
data.tar.gz: 9ea2d0969b402e28030d14b1a3c464aafaf8a34d13385056a9853bea7123b5b1e23e10d7ec602ba7f1894a06fbb6b2948fe1beaea158e841092d65bf726101e7
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
## master
|
2
|
+
|
3
|
+
### enhancements
|
4
|
+
|
5
|
+
- Remove unnecessary joins when performing algebraic simplification
|
6
|
+
[@lowjoel](https://github.com/lowjoel)
|
7
|
+
- Allow querying an unloaded association using a database query. For rules which reference
|
8
|
+
associations, and the association is not currently loaded, an SQL query will be executed
|
9
|
+
instead to verify the rule instead of loading all rows to memory
|
10
|
+
[@lowjoel](https://github.com/lowjoel)
|
11
|
+
- Add Frozen String Literal directive [@lowjoel](https://github.com/lowjoel)
|
12
|
+
|
13
|
+
## 0.1.1
|
14
|
+
|
15
|
+
### enhancements
|
16
|
+
|
17
|
+
- Added usage instructions. [@lowjoel](https://github.com/lowjoel)
|
18
|
+
|
19
|
+
### bug fixes
|
20
|
+
|
21
|
+
- Follow CanCanCan's default ability rule priority [@lowjoel](https://github.com/lowjoel)
|
22
|
+
- Fix generation of comparison operators for array values [@lowjoel](https://github.com/lowjoel)
|
23
|
+
|
24
|
+
## 0.1.0
|
25
|
+
|
26
|
+
Initial release.
|
data/README.md
CHANGED
@@ -11,3 +11,13 @@ uses
|
|
11
11
|
- `WHERE` fragments, joined lexically using `OR` or `AND` or `NOT`.
|
12
12
|
|
13
13
|
As a side effect of using `squeel`, this allows self-joins in rule definitions.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
In your `Gemfile`, insert the following line:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'cancancan-squeel'
|
21
|
+
```
|
22
|
+
|
23
|
+
after you included `cancancan`.
|
data/cancancan-squeel.gemspec
CHANGED
@@ -8,6 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = CanCanCan::Squeel::VERSION
|
9
9
|
spec.authors = ['Joel Low']
|
10
10
|
spec.email = ['joel@joelsplace.sg']
|
11
|
+
spec.license = 'MIT'
|
11
12
|
|
12
13
|
spec.summary = 'Squeel database adapter for CanCanCan.'
|
13
14
|
spec.description = "Implements CanCanCan's rule-based record fetching using Squeel."
|
data/lib/cancancan/squeel.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_record'
|
2
3
|
require 'cancancan'
|
3
4
|
require 'squeel'
|
4
5
|
|
5
6
|
require 'cancancan/squeel/version'
|
6
7
|
require 'cancancan/squeel/active_record_disabler'
|
8
|
+
require 'cancancan/squeel/attribute_mapper'
|
9
|
+
require 'cancancan/squeel/expression_builder'
|
10
|
+
require 'cancancan/squeel/expression_combinator'
|
7
11
|
require 'cancancan/squeel/squeel_adapter'
|
8
|
-
|
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class CanCanCan::Squeel::ActiveRecordDisabler
|
3
|
+
::CanCan::ModelAdapters::ActiveRecord4Adapter.class_eval do
|
4
|
+
def self.for_class?(_)
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Implements mapping attributes, values, and comparators for a given model to appropriate
|
3
|
+
# database equivalents.
|
4
|
+
#
|
5
|
+
# This implements:
|
6
|
+
# - comparing values against an array: interpreted as any value for ==, none of the values for !=.
|
7
|
+
# - mapping foreign keys to IDs
|
8
|
+
module CanCanCan::Squeel::AttributeMapper
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Picks the appropriate column, comparator, and value to use in the Squeel expression.
|
12
|
+
#
|
13
|
+
# This checks for association references: this will use the appropriate column name.
|
14
|
+
#
|
15
|
+
# Array values are interpreted as alternative choices allowed or disallowed.
|
16
|
+
#
|
17
|
+
# @param [Class] model_class The model class which the key references.
|
18
|
+
# @param [Symbol] key The column being compared.
|
19
|
+
# @param [Symbol] comparator The comparator to get the appropriate Squeel comparator for.
|
20
|
+
# @param value The value to be comparing against.
|
21
|
+
# @return [Array<(Symbol, Symbol, Object)>] A triple containing the column to compare with, the
|
22
|
+
# comparator to use, and the value to compare with.
|
23
|
+
def squeel_comparison_for(model_class, key, comparator, value)
|
24
|
+
key, value = map_association(model_class, key, value)
|
25
|
+
|
26
|
+
comparator = squeel_comparator_for(comparator, value)
|
27
|
+
[key, comparator, value]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Picks the table column to compare the value against for the given key.
|
31
|
+
#
|
32
|
+
# This sets associations to use the proper foreign key column.
|
33
|
+
#
|
34
|
+
# @param [Class] model_class The model class which the key references.
|
35
|
+
# @param [Symbol] key The column being compared.
|
36
|
+
# @param value The value to be comparing against.
|
37
|
+
# @return [Array<(Symbol, Object)>] A tuple containing the column to compare with and the value
|
38
|
+
# to compare with.
|
39
|
+
def map_association(model_class, key, value)
|
40
|
+
if (association = model_class.reflect_on_association(key))
|
41
|
+
key = association.foreign_key
|
42
|
+
end
|
43
|
+
|
44
|
+
[key, value]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Maps the given comparator to a comparator appropriate for the given value.
|
48
|
+
#
|
49
|
+
# Array values are interpreted as alternative choices allowed or disallowed.
|
50
|
+
#
|
51
|
+
# @param [Symbol] comparator The comparator to get the appropriate Squeel comparator for.
|
52
|
+
# @param value The value to be comparing against.
|
53
|
+
# @return [Symbol] The comparator for the desired effect, suitable for the given type.
|
54
|
+
def squeel_comparator_for(comparator, value)
|
55
|
+
if value.is_a?(Array)
|
56
|
+
case comparator
|
57
|
+
when :== then :>>
|
58
|
+
when :!= then :<<
|
59
|
+
end
|
60
|
+
else
|
61
|
+
comparator
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Builds Squeel expressions from the given scope, model class, and a hash of conditions.
|
3
|
+
#
|
4
|
+
# This is used by building a set of rules for retrieving all accessible records, as well as for
|
5
|
+
# building queries instead of loading all records into memory.
|
6
|
+
module CanCanCan::Squeel::ExpressionBuilder
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Builds a new Squeel expression node given a model class, the comparator, and the conditions.
|
10
|
+
#
|
11
|
+
# @param squeel The Squeel context. This is the value of +self+ within a +where+ DSL block.
|
12
|
+
# @param [Class] model_class The model class which the conditions reference.
|
13
|
+
# @param [Symbol] comparator The comparator to use when generating the comparison.
|
14
|
+
# @param [Hash] conditions The values to compare the given node's attributes against.
|
15
|
+
# @return [Array<(Squeel::Nodes::Node, Array<Array<Symbol>>)>] A tuple containing the Squeel
|
16
|
+
# expression representing the rule's conditions, as well as an array of joins which the Squeel
|
17
|
+
# expression must be joined to.
|
18
|
+
def build(squeel, model_class, comparator, conditions)
|
19
|
+
build_expression_node(squeel, model_class, comparator, conditions, true)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Builds a new Squeel expression node.
|
23
|
+
#
|
24
|
+
# @param node The parent node context.
|
25
|
+
# @param [Class] model_class The model class which the conditions reference.
|
26
|
+
# @param [Symbol] comparator The comparator to use when generating the comparison.
|
27
|
+
# @param [Hash] conditions The values to compare the given node's attributes against.
|
28
|
+
# @param [Boolean] root True if the node being built is from the root. The root node is special
|
29
|
+
# because it does not mutate itself; all other nodes do.
|
30
|
+
# @return [Array<(Squeel::Nodes::Node, Array<Array<Symbol>>)>] A tuple containing the Squeel
|
31
|
+
# expression representing the rule's conditions, as well as an array of joins which the Squeel
|
32
|
+
# expression must be joined to.
|
33
|
+
def build_expression_node(node, model_class, comparator, conditions, root = false)
|
34
|
+
conditions.reduce([nil, []]) do |(left_expression, joins), (key, value)|
|
35
|
+
comparison_node, node_joins = build_comparison_node(root ? node : node.dup, model_class,
|
36
|
+
key, comparator, value)
|
37
|
+
if left_expression
|
38
|
+
[left_expression & comparison_node, joins.concat(node_joins)]
|
39
|
+
else
|
40
|
+
[comparison_node, node_joins]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Builds a comparison node for the given key and value.
|
46
|
+
#
|
47
|
+
# @param node The node context to build the comparison.
|
48
|
+
# @param [Class] model_class The model class which the conditions reference.
|
49
|
+
# @param [Symbol] key The column to compare against.
|
50
|
+
# @param [Symbol] comparator The comparator to compare the column against the value.
|
51
|
+
# @param value The value to compare the column against.
|
52
|
+
def build_comparison_node(node, model_class, key, comparator, value)
|
53
|
+
if value.is_a?(Hash)
|
54
|
+
reflection_class = model_class.reflect_on_association(key).klass
|
55
|
+
expression, joins = build_expression_node(node.__send__(key), reflection_class, comparator,
|
56
|
+
value)
|
57
|
+
[expression, joins.map { |join| join.unshift(key) }.unshift([key])]
|
58
|
+
else
|
59
|
+
key, comparator, value = CanCanCan::Squeel::AttributeMapper.
|
60
|
+
squeel_comparison_for(model_class, key, comparator, value)
|
61
|
+
[node.__send__(key).public_send(comparator, value), []]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CanCanCan::Squeel::ExpressionCombinator
|
3
|
+
# This true expression is used to indicate a condition that is always satisfied.
|
4
|
+
ALWAYS_TRUE = Squeel::Nodes::Predicate.new(Squeel::Nodes::Literal.new('1'), :eq, 1).freeze
|
5
|
+
|
6
|
+
# This true expression is used to indicate a condition that is never satisfied.
|
7
|
+
ALWAYS_FALSE = Squeel::Nodes::Predicate.new(Squeel::Nodes::Literal.new('1'), :eq, 0).freeze
|
8
|
+
|
9
|
+
# Combines two Squeel expressions. This is aware of the +ALWAYS_TRUE+ and +ALWAYS_FALSE+
|
10
|
+
# constants and performs simplification.
|
11
|
+
#
|
12
|
+
# @param [Squeel::Nodes::Node] left_expression The left expression.
|
13
|
+
# @param [Array] left_expression_joins An array of joins which the Squeel expression must be
|
14
|
+
# joined to.
|
15
|
+
# @param [Symbol] operator The operator to combine with. This must be either +:&+ or +:|+.
|
16
|
+
# @param [Squeel::Nodes::Node] right_expression The right expression.
|
17
|
+
# @param [Array] right_expression_joins An array of joins which the Squeel expression must be
|
18
|
+
# joined to.
|
19
|
+
# @return [Array<(Squeel::Nodes::Node, Array)>] A tuple containing the combination of the given
|
20
|
+
# expressions, as well as an array of joins which the Squeel expression must be joined to.
|
21
|
+
def combine_squeel_expressions(left_expression, left_expression_joins, operator,
|
22
|
+
right_expression, right_expression_joins)
|
23
|
+
case operator
|
24
|
+
when :& then conjunction_expressions(left_expression, left_expression_joins,
|
25
|
+
right_expression, right_expression_joins)
|
26
|
+
when :| then disjunction_expressions(left_expression, left_expression_joins,
|
27
|
+
right_expression, right_expression_joins)
|
28
|
+
else
|
29
|
+
raise ArgumentError, "#{operator} must either be :& or :|"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Computes the conjunction of the two Squeel expressions.
|
34
|
+
#
|
35
|
+
# Boolean simplification is done for the +ALWAYS_TRUE+ and +ALWAYS_FALSE+ values.
|
36
|
+
# @param [Squeel::Nodes::Node] left_expression The left expression.
|
37
|
+
# @param [Array] left_expression_joins An array of joins which the Squeel expression must be
|
38
|
+
# joined to.
|
39
|
+
# @param [Squeel::Nodes::Node] right_expression The right expression.
|
40
|
+
# @param [Array] right_expression_joins An array of joins which the Squeel expression must be
|
41
|
+
# joined to.
|
42
|
+
# @return [Array<(Squeel::Nodes::Node, Array)>] A tuple containing the conjunction of the left and
|
43
|
+
# right expressions, as well as an array of joins which the Squeel expression must be joined to.
|
44
|
+
def conjunction_expressions(left_expression, left_expression_joins, right_expression,
|
45
|
+
right_expression_joins)
|
46
|
+
if left_expression == ALWAYS_FALSE || right_expression == ALWAYS_FALSE
|
47
|
+
[ALWAYS_FALSE, []]
|
48
|
+
elsif left_expression == ALWAYS_TRUE
|
49
|
+
[right_expression, right_expression_joins]
|
50
|
+
elsif right_expression == ALWAYS_TRUE
|
51
|
+
[left_expression, left_expression_joins]
|
52
|
+
else
|
53
|
+
[left_expression & right_expression, left_expression_joins + right_expression_joins]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Computes the disjunction of the two Squeel expressions.
|
58
|
+
#
|
59
|
+
# Boolean simplification is done for the +ALWAYS_TRUE+ and +ALWAYS_FALSE+ values.
|
60
|
+
# @param [Squeel::Nodes::Node] left_expression The left expression.
|
61
|
+
# @param [Array] left_expression_joins An array of joins which the Squeel expression must be
|
62
|
+
# joined to.
|
63
|
+
# @param [Squeel::Nodes::Node] right_expression The right expression.
|
64
|
+
# @param [Array] right_expression_joins An array of joins which the Squeel expression must be
|
65
|
+
# joined to.
|
66
|
+
# @return [Array<(Squeel::Nodes::Node, Array)>] A tuple containing the disjunction of the left and
|
67
|
+
# right expressions, as well as an array of joins which the Squeel expression must be joined to.
|
68
|
+
def disjunction_expressions(left_expression, left_expression_joins, right_expression,
|
69
|
+
right_expression_joins)
|
70
|
+
if left_expression == ALWAYS_TRUE || right_expression == ALWAYS_TRUE
|
71
|
+
[ALWAYS_TRUE, []]
|
72
|
+
elsif left_expression == ALWAYS_FALSE
|
73
|
+
[right_expression, right_expression_joins]
|
74
|
+
elsif right_expression == ALWAYS_FALSE
|
75
|
+
[left_expression, left_expression_joins]
|
76
|
+
else
|
77
|
+
[left_expression | right_expression, left_expression_joins + right_expression_joins]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,15 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class CanCanCan::Squeel::SqueelAdapter < CanCan::ModelAdapters::AbstractAdapter
|
3
|
+
include CanCanCan::Squeel::ExpressionCombinator
|
4
|
+
|
5
|
+
ALWAYS_TRUE = CanCanCan::Squeel::ExpressionCombinator::ALWAYS_TRUE
|
6
|
+
ALWAYS_FALSE = CanCanCan::Squeel::ExpressionCombinator::ALWAYS_FALSE
|
7
|
+
|
2
8
|
def self.for_class?(model_class)
|
3
9
|
model_class <= ActiveRecord::Base
|
4
10
|
end
|
5
11
|
|
6
12
|
def self.override_condition_matching?(subject, name, _)
|
7
|
-
|
8
|
-
|
9
|
-
subject.class.defined_enums.include?(name.to_s)
|
13
|
+
match_relation?(subject, name) || match_enum?(subject, name)
|
10
14
|
end
|
11
15
|
|
12
16
|
def self.matches_condition?(subject, name, value)
|
17
|
+
if match_relation?(subject, name)
|
18
|
+
matches_relation?(subject, name, value)
|
19
|
+
elsif match_enum?(subject, name)
|
20
|
+
matches_enum?(subject, name, value)
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Overrides condition matching for enums.
|
27
|
+
def self.match_enum?(subject, name)
|
28
|
+
klass = subject.class
|
29
|
+
klass.respond_to?(:defined_enums) && klass.defined_enums.include?(name.to_s)
|
30
|
+
end
|
31
|
+
private_class_method :match_enum?
|
32
|
+
|
33
|
+
# Overrides condition matching for enums.
|
34
|
+
def self.matches_enum?(subject, name, value)
|
13
35
|
# Get the mapping from enum strings to values.
|
14
36
|
enum = subject.class.public_send(name.to_s.pluralize)
|
15
37
|
|
@@ -19,6 +41,24 @@ class CanCanCan::Squeel::SqueelAdapter < CanCan::ModelAdapters::AbstractAdapter
|
|
19
41
|
# Check to see if the value matches the condition.
|
20
42
|
value.is_a?(Enumerable) ? value.include?(attribute) : attribute == value
|
21
43
|
end
|
44
|
+
private_class_method :matches_enum?
|
45
|
+
|
46
|
+
def self.match_relation?(subject, name)
|
47
|
+
subject_attribute = subject.public_send(name)
|
48
|
+
subject_attribute.is_a?(ActiveRecord::Relation) && !subject_attribute.loaded
|
49
|
+
end
|
50
|
+
private_class_method :match_relation?
|
51
|
+
|
52
|
+
def self.matches_relation?(subject, name, value)
|
53
|
+
relation = subject.public_send(name)
|
54
|
+
klass = subject.class.reflect_on_association(name).klass
|
55
|
+
|
56
|
+
relation.where do
|
57
|
+
expression, = CanCanCan::Squeel::ExpressionBuilder.build(self, klass, :==, value)
|
58
|
+
expression
|
59
|
+
end.any?
|
60
|
+
end
|
61
|
+
private_class_method :matches_relation?
|
22
62
|
|
23
63
|
def database_records
|
24
64
|
# TODO: Handle overridden scopes.
|
@@ -29,32 +69,17 @@ class CanCanCan::Squeel::SqueelAdapter < CanCan::ModelAdapters::AbstractAdapter
|
|
29
69
|
|
30
70
|
# Builds a relation that expresses the set of provided rules.
|
31
71
|
#
|
32
|
-
#
|
33
|
-
#
|
72
|
+
# The required Squeel expression is built, then the joins which are necessary to satisfy the
|
73
|
+
# expressions are added to the query scope.
|
34
74
|
def relation
|
35
|
-
|
36
|
-
|
75
|
+
adapter = self
|
76
|
+
join_list = nil
|
77
|
+
scope = @model_class.where(nil).where do
|
78
|
+
expression, join_list = adapter.send(:build_accessible_by_expression, self)
|
79
|
+
expression
|
37
80
|
end
|
38
81
|
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# Builds an array of joins for the given conditions hash.
|
43
|
-
#
|
44
|
-
# For example:
|
45
|
-
#
|
46
|
-
# a: { b: { c: 3 }, d: { e: 4 }} => [[:a, :b], [:a, :d]]
|
47
|
-
#
|
48
|
-
# @param [Hash] conditions The conditions to build the joins.
|
49
|
-
# @return [Array<Array<Symbol>>] The joins needed to satisfy the given conditions
|
50
|
-
def build_join_list(conditions)
|
51
|
-
conditions.flat_map do |key, value|
|
52
|
-
if value.is_a?(Hash)
|
53
|
-
[[key]].concat(build_join_list(value).map { |join| Array(join).unshift(key) })
|
54
|
-
else
|
55
|
-
[]
|
56
|
-
end
|
57
|
-
end
|
82
|
+
add_joins_to_scope(scope, join_list)
|
58
83
|
end
|
59
84
|
|
60
85
|
# Builds a relation, outer joined on the provided associations.
|
@@ -72,24 +97,17 @@ class CanCanCan::Squeel::SqueelAdapter < CanCan::ModelAdapters::AbstractAdapter
|
|
72
97
|
end
|
73
98
|
end
|
74
99
|
|
75
|
-
# Adds the rule conditions to the scope.
|
76
|
-
#
|
77
100
|
# This builds Squeel expression for each rule, and combines the expression with those to the left
|
78
101
|
# using a fold-left.
|
79
102
|
#
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
combined_rule = adapter.send(:combine_expression_with_rule, self, left_expression, rule)
|
89
|
-
break if combined_rule.nil?
|
90
|
-
|
91
|
-
combined_rule
|
92
|
-
end
|
103
|
+
# The rules provided by Cancancan are in reverse order, i.e. the lowest priority rule is first.
|
104
|
+
#
|
105
|
+
# @param squeel The Squeel scope.
|
106
|
+
# @return [Array<(Squeel::Nodes::Node, Array<Array<Symbol>>)>] A tuple containing the Squeel
|
107
|
+
# expression, as well as an array of joins which the Squeel expression must be joined to.
|
108
|
+
def build_accessible_by_expression(squeel)
|
109
|
+
@rules.reverse.reduce([ALWAYS_FALSE, []]) do |(left_expression, joins), rule|
|
110
|
+
combine_expression_with_rule(squeel, left_expression, joins, rule)
|
93
111
|
end
|
94
112
|
end
|
95
113
|
|
@@ -97,99 +115,32 @@ class CanCanCan::Squeel::SqueelAdapter < CanCan::ModelAdapters::AbstractAdapter
|
|
97
115
|
#
|
98
116
|
# @param squeel The Squeel scope.
|
99
117
|
# @param left_expression The Squeel expression for all preceding rules.
|
118
|
+
# @param [Array<Array<Symbol>>] joins An array of joins which the Squeel expression must be
|
119
|
+
# joined to.
|
100
120
|
# @param [CanCan::Rule] rule The rule being added.
|
101
|
-
# @return [Squeel::Nodes::Node]
|
102
|
-
#
|
103
|
-
def combine_expression_with_rule(squeel, left_expression, rule)
|
104
|
-
right_expression = build_expression_from_rule(squeel, rule)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
else
|
110
|
-
left_expression & right_expression
|
111
|
-
end
|
121
|
+
# @return [Array<(Squeel::Nodes::Node, Array<Array<Symbol>>)>] A tuple containing the Squeel
|
122
|
+
# expression, as well as an array of joins which the Squeel expression must be joined to.
|
123
|
+
def combine_expression_with_rule(squeel, left_expression, joins, rule)
|
124
|
+
right_expression, right_expression_joins = build_expression_from_rule(squeel, rule)
|
125
|
+
|
126
|
+
operator = rule.base_behavior ? :| : :&
|
127
|
+
combine_squeel_expressions(left_expression, joins, operator, right_expression,
|
128
|
+
right_expression_joins)
|
112
129
|
end
|
113
130
|
|
114
131
|
# Builds a Squeel expression representing the rule's conditions.
|
115
132
|
#
|
116
133
|
# @param squeel The Squeel scope.
|
117
134
|
# @param [CanCan::Rule] rule The rule being built.
|
135
|
+
# @return [Array<(Squeel::Nodes::Node, Array<Array<Symbol>>)>] A tuple containing the Squeel
|
136
|
+
# expression representing the rule's conditions, as well as an array of joins which the Squeel
|
137
|
+
# expression must be joined to.
|
118
138
|
def build_expression_from_rule(squeel, rule)
|
119
|
-
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
# Builds a new Squeel expression node.
|
124
|
-
#
|
125
|
-
# @param node The parent node context.
|
126
|
-
# @param [Class] model_class The model class which the conditions reference.
|
127
|
-
# @param [Symbol] comparator The comparator to use when generating the comparison.
|
128
|
-
# @param [Hash] conditions The values to compare the given node's attributes against.
|
129
|
-
# @param [Boolean] root True if the node being built is from the root. The root node is special
|
130
|
-
# because it does not mutate itself; all other nodes do.
|
131
|
-
def build_expression_node(node, model_class, comparator, conditions, root = false)
|
132
|
-
conditions.reduce(nil) do |left_expression, (key, value)|
|
133
|
-
comparison_node = build_comparison_node(root ? node : node.dup, model_class, key,
|
134
|
-
comparator, value)
|
135
|
-
if left_expression
|
136
|
-
left_expression & comparison_node
|
137
|
-
else
|
138
|
-
comparison_node
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# Builds a comparison node for the given key and value.
|
144
|
-
#
|
145
|
-
# @param node The node context to build the comparison.
|
146
|
-
# @param [Class] model_class The model class which the conditions reference.
|
147
|
-
# @param [Symbol] key The column to compare against.
|
148
|
-
# @param [Symbol] comparator The comparator to compare the column against the value.
|
149
|
-
# @param value The value to compare the column against.
|
150
|
-
def build_comparison_node(node, model_class, key, comparator, value)
|
151
|
-
if value.is_a?(Hash)
|
152
|
-
reflection = model_class.reflect_on_association(key)
|
153
|
-
build_expression_node(node.__send__(key), reflection.klass, comparator, value)
|
139
|
+
if rule.conditions.empty?
|
140
|
+
[rule.base_behavior ? ALWAYS_TRUE : ALWAYS_FALSE, []]
|
154
141
|
else
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
# Picks the appropriate column, comparator, and value to use in the Squeel expression.
|
161
|
-
#
|
162
|
-
# This checks for association references: this will use the appropriate column name.
|
163
|
-
#
|
164
|
-
# Array values are interpreted as alternative choices allowed or disallowed.
|
165
|
-
#
|
166
|
-
# @param [Class] model_class The model class which the key references.
|
167
|
-
# @param [Symbol] key The column being compared.
|
168
|
-
# @param [Symbol] comparator The comparator to get the appropriate Squeel comparator for.
|
169
|
-
# @param value The value to be comparing against.
|
170
|
-
# @return [Array<(Symbol, Symbol, Object)>] A triple containing the column to compare with, the
|
171
|
-
# comparator to use, and the value to compare with.
|
172
|
-
def squeel_comparison_for(model_class, key, comparator, value)
|
173
|
-
if (association = model_class.reflect_on_association(key))
|
174
|
-
key = association.foreign_key
|
175
|
-
end
|
176
|
-
|
177
|
-
comparator = squeel_comparator_for(comparator, value)
|
178
|
-
[key, comparator, value]
|
179
|
-
end
|
180
|
-
|
181
|
-
# Maps the given comparator to a comparator appropriate for the given value.
|
182
|
-
#
|
183
|
-
# Array values are interpreted as alternative choices allowed or disallowed.
|
184
|
-
#
|
185
|
-
# @param [Symbol] comparator The comparator to get the appropriate Squeel comparator for.
|
186
|
-
# @param value The value to be comparing against.
|
187
|
-
# @return [Symbol] The comparator for the desired effect, suitable for the given type.
|
188
|
-
def squeel_comparator_for(comparator, value)
|
189
|
-
case [comparator, value]
|
190
|
-
when :==, Array then :>>
|
191
|
-
when :!=, Array then :<<
|
192
|
-
else comparator
|
142
|
+
comparator = rule.base_behavior ? :== : :!=
|
143
|
+
CanCanCan::Squeel::ExpressionBuilder.build(squeel, @model_class, comparator, rule.conditions)
|
193
144
|
end
|
194
145
|
end
|
195
146
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
2
|
-
module CanCanCan
|
3
|
-
|
4
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CanCanCan; end
|
3
|
+
module CanCanCan::Squeel
|
4
|
+
VERSION = '0.1.2'
|
5
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cancancan-squeel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Low
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -164,16 +164,21 @@ files:
|
|
164
164
|
- ".rubocop.unhound.yml"
|
165
165
|
- ".rubocop.yml"
|
166
166
|
- ".travis.yml"
|
167
|
+
- CHANGELOG.md
|
167
168
|
- Gemfile
|
168
169
|
- README.md
|
169
170
|
- Rakefile
|
170
171
|
- cancancan-squeel.gemspec
|
171
172
|
- lib/cancancan/squeel.rb
|
172
173
|
- lib/cancancan/squeel/active_record_disabler.rb
|
174
|
+
- lib/cancancan/squeel/attribute_mapper.rb
|
175
|
+
- lib/cancancan/squeel/expression_builder.rb
|
176
|
+
- lib/cancancan/squeel/expression_combinator.rb
|
173
177
|
- lib/cancancan/squeel/squeel_adapter.rb
|
174
178
|
- lib/cancancan/squeel/version.rb
|
175
179
|
homepage: https://github.com/lowjoel/cancancan-squeel
|
176
|
-
licenses:
|
180
|
+
licenses:
|
181
|
+
- MIT
|
177
182
|
metadata: {}
|
178
183
|
post_install_message:
|
179
184
|
rdoc_options: []
|