cancancan-squeel 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e54134fb57f78cfd282b96b03b2bc49cf164468
4
- data.tar.gz: 1c5b7a4a8f1aeea3cca907c7755a8a3fc9f4d13c
3
+ metadata.gz: edbfe73803fc60f8f004514ca962dcb92818cbb4
4
+ data.tar.gz: fc92e4d84a7557edf512293ebcfefd8995496d82
5
5
  SHA512:
6
- metadata.gz: 0b03fa5bfafc638b66de73e42856e3047de0450898f2f6b31b0b4c2f514b4ffbeec719b6b6a35e8d03c09f129351a21c97729d41ecdb1c414e64891ff9901fbb
7
- data.tar.gz: 9b2cee5e00ea453850b5065b826ae5917ebfbd9dd0017bd5870f7770f5802797ece8169f8652af4a33b11bcb219af4af1af4fd925b78dd9175ab52160bdf7c9d
6
+ metadata.gz: ca8e2455b16e3bc007829341fffdce9682cdebfccc1089e1f0874a6edfcdc35433a955388814dd79b929e79a43e55b0b198fcd959587429d40fba178a4d9fe39
7
+ data.tar.gz: 9ea2d0969b402e28030d14b1a3c464aafaf8a34d13385056a9853bea7123b5b1e23e10d7ec602ba7f1894a06fbb6b2948fe1beaea158e841092d65bf726101e7
data/.rubocop.yml CHANGED
@@ -7,7 +7,7 @@ AllCops:
7
7
  - '**/Rakefile'
8
8
  Exclude:
9
9
  - 'vendor/bundle/**/*'
10
- TargetRubyVersion: 2.1
10
+ TargetRubyVersion: 2.3
11
11
 
12
12
  Metrics/LineLength:
13
13
  Max: 100
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`.
@@ -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."
@@ -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
- class CanCanCan::Squeel::ActiveRecordDisabler
2
- ::CanCan::ModelAdapters::ActiveRecord4Adapter.class_eval do
3
- def self.for_class?(_)
4
- false
5
- end
6
- end
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
- return false unless subject.class.respond_to?(:defined_enums)
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
- # This first joins all the tables specified in the rules, then builds the corresponding Squeel
33
- # expression for the conditions.
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
- join_scope = @rules.reduce(@model_class.where(nil)) do |scope, rule|
36
- add_joins_to_scope(scope, build_join_list(rule.conditions))
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
- add_conditions_to_scope(join_scope)
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
- # @param [ActiveRecord::Relation] scope The scope to add the rule conditions to.
81
- def add_conditions_to_scope(scope)
82
- adapter = self
83
- rules = @rules
84
-
85
- # default n
86
- scope.where do
87
- rules.reduce(nil) do |left_expression, rule|
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] If the rule has an expression.
102
- # @return [NilClass] If the rule is unconditional.
103
- def combine_expression_with_rule(squeel, left_expression, rule)
104
- right_expression = build_expression_from_rule(squeel, rule)
105
- return right_expression if right_expression.nil? || !left_expression
106
-
107
- if rule.base_behavior
108
- left_expression | right_expression
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
- comparator = rule.base_behavior ? :== : :!=
120
- build_expression_node(squeel, @model_class, comparator, rule.conditions, true)
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
- key, comparator, value = squeel_comparison_for(model_class, key, comparator, value)
156
- node.__send__(key).public_send(comparator, value)
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
- module CanCanCan; end
2
- module CanCanCan::Squeel
3
- VERSION = '0.1.0'.freeze
4
- end
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.0
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-29 00:00:00.000000000 Z
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: []