mini-lite-tool 0.0.1

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/cancancan-3.6.1/cancancan.gemspec +29 -0
  3. data/cancancan-3.6.1/init.rb +3 -0
  4. data/cancancan-3.6.1/lib/cancan/ability/actions.rb +93 -0
  5. data/cancancan-3.6.1/lib/cancan/ability/rules.rb +96 -0
  6. data/cancancan-3.6.1/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/cancancan-3.6.1/lib/cancan/ability.rb +312 -0
  8. data/cancancan-3.6.1/lib/cancan/class_matcher.rb +30 -0
  9. data/cancancan-3.6.1/lib/cancan/conditions_matcher.rb +147 -0
  10. data/cancancan-3.6.1/lib/cancan/config.rb +101 -0
  11. data/cancancan-3.6.1/lib/cancan/controller_additions.rb +399 -0
  12. data/cancancan-3.6.1/lib/cancan/controller_resource.rb +141 -0
  13. data/cancancan-3.6.1/lib/cancan/controller_resource_builder.rb +26 -0
  14. data/cancancan-3.6.1/lib/cancan/controller_resource_finder.rb +42 -0
  15. data/cancancan-3.6.1/lib/cancan/controller_resource_loader.rb +120 -0
  16. data/cancancan-3.6.1/lib/cancan/controller_resource_name_finder.rb +23 -0
  17. data/cancancan-3.6.1/lib/cancan/controller_resource_sanitizer.rb +32 -0
  18. data/cancancan-3.6.1/lib/cancan/exceptions.rb +70 -0
  19. data/cancancan-3.6.1/lib/cancan/matchers.rb +56 -0
  20. data/cancancan-3.6.1/lib/cancan/model_adapters/abstract_adapter.rb +77 -0
  21. data/cancancan-3.6.1/lib/cancan/model_adapters/active_record_4_adapter.rb +62 -0
  22. data/cancancan-3.6.1/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
  23. data/cancancan-3.6.1/lib/cancan/model_adapters/active_record_adapter.rb +229 -0
  24. data/cancancan-3.6.1/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  25. data/cancancan-3.6.1/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  26. data/cancancan-3.6.1/lib/cancan/model_adapters/default_adapter.rb +9 -0
  27. data/cancancan-3.6.1/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  28. data/cancancan-3.6.1/lib/cancan/model_adapters/strategies/base.rb +40 -0
  29. data/cancancan-3.6.1/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  30. data/cancancan-3.6.1/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  31. data/cancancan-3.6.1/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  32. data/cancancan-3.6.1/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  33. data/cancancan-3.6.1/lib/cancan/model_additions.rb +34 -0
  34. data/cancancan-3.6.1/lib/cancan/parameter_validators.rb +9 -0
  35. data/cancancan-3.6.1/lib/cancan/relevant.rb +29 -0
  36. data/cancancan-3.6.1/lib/cancan/rule.rb +140 -0
  37. data/cancancan-3.6.1/lib/cancan/rules_compressor.rb +41 -0
  38. data/cancancan-3.6.1/lib/cancan/sti_detector.rb +12 -0
  39. data/cancancan-3.6.1/lib/cancan/unauthorized_message_resolver.rb +24 -0
  40. data/cancancan-3.6.1/lib/cancan/version.rb +5 -0
  41. data/cancancan-3.6.1/lib/cancan.rb +29 -0
  42. data/cancancan-3.6.1/lib/cancancan.rb +6 -0
  43. data/cancancan-3.6.1/lib/generators/cancan/ability/USAGE +4 -0
  44. data/cancancan-3.6.1/lib/generators/cancan/ability/ability_generator.rb +13 -0
  45. data/cancancan-3.6.1/lib/generators/cancan/ability/templates/ability.rb +32 -0
  46. data/mini-lite-tool.gemspec +11 -0
  47. metadata +85 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class ActiveRecord5Adapter < ActiveRecord4Adapter
6
+ AbstractAdapter.inherited(self)
7
+
8
+ def self.for_class?(model_class)
9
+ version_greater_or_equal?('5.0.0') && model_class <= ActiveRecord::Base
10
+ end
11
+
12
+ # rails 5 is capable of using strings in enum
13
+ # but often people use symbols in rules
14
+ def self.matches_condition?(subject, name, value)
15
+ return super if Array.wrap(value).all? { |x| x.is_a? Integer }
16
+
17
+ attribute = subject.send(name)
18
+ raw_attribute = subject.class.send(name.to_s.pluralize)[attribute]
19
+ !(Array(value).map(&:to_s) & [attribute, raw_attribute]).empty?
20
+ end
21
+
22
+ private
23
+
24
+ def build_joins_relation(relation, *where_conditions)
25
+ strategy_class.new(adapter: self, relation: relation, where_conditions: where_conditions).execute!
26
+ end
27
+
28
+ def strategy_class
29
+ strategy_class_name = CanCan.accessible_by_strategy.to_s.camelize
30
+ CanCan::ModelAdapters::Strategies.const_get(strategy_class_name)
31
+ end
32
+
33
+ def sanitize_sql(conditions)
34
+ if conditions.is_a?(Hash)
35
+ sanitize_sql_activerecord5(conditions)
36
+ else
37
+ @model_class.send(:sanitize_sql, conditions)
38
+ end
39
+ end
40
+
41
+ def sanitize_sql_activerecord5(conditions)
42
+ table = @model_class.send(:arel_table)
43
+ table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
44
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
45
+
46
+ predicate_builder.build_from_hash(conditions.stringify_keys).map { |b| visit_nodes(b) }.join(' AND ')
47
+ end
48
+
49
+ def visit_nodes(node)
50
+ # Rails 5.2 adds a BindParam node that prevents the visitor method from properly compiling the SQL query
51
+ if self.class.version_greater_or_equal?('5.2.0')
52
+ connection = @model_class.send(:connection)
53
+ collector = Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new)
54
+ connection.visitor.accept(node, collector).value
55
+ else
56
+ @model_class.send(:connection).visitor.compile(node)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/AbcSize
4
+ # rubocop:disable Metrics/CyclomaticComplexity
5
+ # rubocop:disable Metrics/PerceivedComplexity
6
+ module CanCan
7
+ module ModelAdapters
8
+ class ActiveRecordAdapter < AbstractAdapter
9
+ def self.version_greater_or_equal?(version)
10
+ Gem::Version.new(ActiveRecord.version).release >= Gem::Version.new(version)
11
+ end
12
+
13
+ def self.version_lower?(version)
14
+ Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
15
+ end
16
+
17
+ attr_reader :compressed_rules
18
+
19
+ def initialize(model_class, rules)
20
+ super
21
+ @compressed_rules = if CanCan.rules_compressor_enabled
22
+ RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
23
+ else
24
+ @rules
25
+ end
26
+ StiNormalizer.normalize(@compressed_rules)
27
+ ConditionsNormalizer.normalize(model_class, @compressed_rules)
28
+ end
29
+
30
+ class << self
31
+ # When belongs_to parent_id is a condition for a model,
32
+ # we want to check the parent when testing ability for a hash {parent => model}
33
+ def override_nested_subject_conditions_matching?(parent, child, all_conditions)
34
+ parent_child_conditions(parent, child, all_conditions).present?
35
+ end
36
+
37
+ # parent_id condition can be an array of integer or one integer, we check the parent against this
38
+ def nested_subject_matches_conditions?(parent, child, all_conditions)
39
+ id_condition = parent_child_conditions(parent, child, all_conditions)
40
+ return id_condition.include?(parent.id) if id_condition.is_a? Array
41
+ return id_condition == parent.id if id_condition.is_a? Integer
42
+
43
+ false
44
+ end
45
+
46
+ def parent_child_conditions(parent, child, all_conditions)
47
+ child_class = child.is_a?(Class) ? child : child.class
48
+ parent_class = parent.is_a?(Class) ? parent : parent.class
49
+
50
+ foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association|
51
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
52
+ !association.polymorphic? && association.klass == parent.class
53
+ end&.foreign_key&.to_sym
54
+
55
+ # Search again in case of polymorphic associations, this time matching on the :has_many side
56
+ # via the :as option, as well as klass
57
+ foreign_key ||= parent_class.reflect_on_all_associations(:has_many).find do |has_many_assoc|
58
+ matching_parent_child_polymorphic_association(has_many_assoc, child_class)
59
+ end&.foreign_key&.to_sym
60
+
61
+ foreign_key.nil? ? nil : all_conditions[foreign_key]
62
+ end
63
+
64
+ def matching_parent_child_polymorphic_association(parent_assoc, child_class)
65
+ return nil unless parent_assoc.klass == child_class
66
+ return nil if parent_assoc&.options[:as].nil?
67
+
68
+ child_class.reflect_on_all_associations(:belongs_to).find do |child_assoc|
69
+ # Only match this way for polymorphic associations
70
+ child_assoc.polymorphic? && child_assoc.name == parent_assoc.options[:as]
71
+ end
72
+ end
73
+
74
+ def child_association_to_parent(parent, child)
75
+ child_class = child.is_a?(Class) ? child : child.class
76
+ parent_class = parent.is_a?(Class) ? parent : parent.class
77
+
78
+ association = child_class.reflect_on_all_associations(:belongs_to).find do |belongs_to_assoc|
79
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
80
+ !belongs_to_assoc.polymorphic? && belongs_to_assoc.klass == parent.class
81
+ end
82
+
83
+ return association if association
84
+
85
+ parent_class.reflect_on_all_associations(:has_many).each do |has_many_assoc|
86
+ association ||= matching_parent_child_polymorphic_association(has_many_assoc, child_class)
87
+ end
88
+
89
+ association
90
+ end
91
+
92
+ def parent_condition_name(parent, child)
93
+ child_association_to_parent(parent, child)&.name || parent.class.name.downcase.to_sym
94
+ end
95
+ end
96
+
97
+ # Returns conditions intended to be used inside a database query. Normally you will not call this
98
+ # method directly, but instead go through ModelAdditions#accessible_by.
99
+ #
100
+ # If there is only one "can" definition, a hash of conditions will be returned matching the one defined.
101
+ #
102
+ # can :manage, User, :id => 1
103
+ # query(:manage, User).conditions # => { :id => 1 }
104
+ #
105
+ # If there are multiple "can" definitions, a SQL string will be returned to handle complex cases.
106
+ #
107
+ # can :manage, User, :id => 1
108
+ # can :manage, User, :manager_id => 1
109
+ # cannot :manage, User, :self_managed => true
110
+ # query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
111
+ #
112
+ def conditions
113
+ conditions_extractor = ConditionsExtractor.new(@model_class)
114
+ if @compressed_rules.size == 1 && @compressed_rules.first.base_behavior
115
+ # Return the conditions directly if there's just one definition
116
+ conditions_extractor.tableize_conditions(@compressed_rules.first.conditions).dup
117
+ else
118
+ extract_multiple_conditions(conditions_extractor, @compressed_rules)
119
+ end
120
+ end
121
+
122
+ def extract_multiple_conditions(conditions_extractor, rules)
123
+ rules.reverse.inject(false_sql) do |sql, rule|
124
+ merge_conditions(sql, conditions_extractor.tableize_conditions(rule.conditions).dup, rule.base_behavior)
125
+ end
126
+ end
127
+
128
+ def database_records
129
+ if override_scope
130
+ @model_class.where(nil).merge(override_scope)
131
+ elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
132
+ build_relation(conditions)
133
+ else
134
+ @model_class.all(conditions: conditions, joins: joins)
135
+ end
136
+ end
137
+
138
+ def build_relation(*where_conditions)
139
+ relation = @model_class.where(*where_conditions)
140
+ return relation unless joins.present?
141
+
142
+ # subclasses must implement `build_joins_relation`
143
+ build_joins_relation(relation, *where_conditions)
144
+ end
145
+
146
+ # Returns the associations used in conditions for the :joins option of a search.
147
+ # See ModelAdditions#accessible_by
148
+ def joins
149
+ joins_hash = {}
150
+ @compressed_rules.reverse_each do |rule|
151
+ deep_merge(joins_hash, rule.associations_hash)
152
+ end
153
+ deep_clean(joins_hash) unless joins_hash.empty?
154
+ end
155
+
156
+ private
157
+
158
+ # Removes empty hashes and moves everything into arrays.
159
+ def deep_clean(joins_hash)
160
+ joins_hash.map { |name, nested| nested.empty? ? name : { name => deep_clean(nested) } }
161
+ end
162
+
163
+ # Takes two hashes and does a deep merge.
164
+ def deep_merge(base_hash, added_hash)
165
+ added_hash.each do |key, value|
166
+ if base_hash[key].is_a?(Hash)
167
+ deep_merge(base_hash[key], value) unless value.empty?
168
+ else
169
+ base_hash[key] = value
170
+ end
171
+ end
172
+ end
173
+
174
+ def override_scope
175
+ conditions = @compressed_rules.map(&:conditions).compact
176
+ return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
177
+ return conditions.first if conditions.size == 1
178
+
179
+ raise_override_scope_error
180
+ end
181
+
182
+ def raise_override_scope_error
183
+ rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
184
+ raise Error,
185
+ 'Unable to merge an Active Record scope with other conditions. ' \
186
+ "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
187
+ end
188
+
189
+ def merge_conditions(sql, conditions_hash, behavior)
190
+ if conditions_hash.blank?
191
+ behavior ? true_sql : false_sql
192
+ else
193
+ merge_non_empty_conditions(behavior, conditions_hash, sql)
194
+ end
195
+ end
196
+
197
+ def merge_non_empty_conditions(behavior, conditions_hash, sql)
198
+ conditions = sanitize_sql(conditions_hash)
199
+ case sql
200
+ when true_sql
201
+ behavior ? true_sql : "not (#{conditions})"
202
+ when false_sql
203
+ behavior ? conditions : false_sql
204
+ else
205
+ behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
206
+ end
207
+ end
208
+
209
+ def false_sql
210
+ sanitize_sql(['?=?', true, false])
211
+ end
212
+
213
+ def true_sql
214
+ sanitize_sql(['?=?', true, true])
215
+ end
216
+
217
+ def sanitize_sql(conditions)
218
+ @model_class.send(:sanitize_sql, conditions)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ # rubocop:enable Metrics/PerceivedComplexity
224
+ # rubocop:enable Metrics/CyclomaticComplexity
225
+ # rubocop:enable Metrics/AbcSize
226
+
227
+ ActiveSupport.on_load(:active_record) do
228
+ send :include, CanCan::ModelAdditions
229
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # this class is responsible of converting the hash of conditions
4
+ # in "where conditions" to generate the sql query
5
+ # it consists of a names_cache that helps calculating the next name given to the association
6
+ # it tries to reflect the behavior of ActiveRecord when generating aliases for tables.
7
+ module CanCan
8
+ module ModelAdapters
9
+ class ConditionsExtractor
10
+ def initialize(model_class)
11
+ @names_cache = { model_class.table_name => [] }.with_indifferent_access
12
+ @root_model_class = model_class
13
+ end
14
+
15
+ def tableize_conditions(conditions, model_class = @root_model_class, path_to_key = 0)
16
+ return conditions unless conditions.is_a? Hash
17
+
18
+ conditions.each_with_object({}) do |(key, value), result_hash|
19
+ if value.is_a? Hash
20
+ result_hash.merge!(calculate_result_hash(key, model_class, path_to_key, result_hash, value))
21
+ else
22
+ result_hash[key] = value
23
+ end
24
+ result_hash
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def calculate_result_hash(key, model_class, path_to_key, result_hash, value)
31
+ reflection = model_class.reflect_on_association(key)
32
+ nested_resulted = calculate_nested(model_class, result_hash, key, value.dup, path_to_key)
33
+ association_class = reflection.klass.name.constantize
34
+ tableize_conditions(nested_resulted, association_class, "#{path_to_key}_#{key}")
35
+ end
36
+
37
+ def calculate_nested(model_class, result_hash, relation_name, value, path_to_key)
38
+ value.each_with_object({}) do |(k, v), nested|
39
+ if v.is_a? Hash
40
+ value.delete(k)
41
+ nested[k] = v
42
+ else
43
+ table_alias = generate_table_alias(model_class, relation_name, path_to_key)
44
+ result_hash[table_alias] = value
45
+ end
46
+ nested
47
+ end
48
+ end
49
+
50
+ def generate_table_alias(model_class, relation_name, path_to_key)
51
+ table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym
52
+
53
+ if already_used?(table_alias, relation_name, path_to_key)
54
+ table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym
55
+
56
+ index = 1
57
+ while already_used?(table_alias, relation_name, path_to_key)
58
+ table_alias = "#{table_alias}_#{index += 1}".to_sym
59
+ end
60
+ end
61
+ add_to_cache(table_alias, relation_name, path_to_key)
62
+ end
63
+
64
+ def already_used?(table_alias, relation_name, path_to_key)
65
+ @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}")
66
+ end
67
+
68
+ def add_to_cache(table_alias, relation_name, path_to_key)
69
+ @names_cache[table_alias] ||= []
70
+ @names_cache[table_alias] << "#{path_to_key}_#{relation_name}"
71
+ table_alias
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,49 @@
1
+ # this class is responsible of normalizing the hash of conditions
2
+ # by exploding has_many through associations
3
+ # when a condition is defined with an has_many through association this is exploded in all its parts
4
+ # TODO: it could identify STI and normalize it
5
+ module CanCan
6
+ module ModelAdapters
7
+ class ConditionsNormalizer
8
+ class << self
9
+ def normalize(model_class, rules)
10
+ rules.each { |rule| rule.conditions = normalize_conditions(model_class, rule.conditions) }
11
+ end
12
+
13
+ def normalize_conditions(model_class, conditions)
14
+ return conditions unless conditions.is_a? Hash
15
+
16
+ conditions.each_with_object({}) do |(key, value), result_hash|
17
+ if value.is_a? Hash
18
+ result_hash.merge!(calculate_result_hash(model_class, key, value))
19
+ else
20
+ result_hash[key] = value
21
+ end
22
+ result_hash
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def calculate_result_hash(model_class, key, value)
29
+ reflection = model_class.reflect_on_association(key)
30
+ unless reflection
31
+ raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'"
32
+ end
33
+
34
+ if normalizable_association? reflection
35
+ key = reflection.options[:through]
36
+ value = { reflection.source_reflection_name => value }
37
+ reflection = model_class.reflect_on_association(key)
38
+ end
39
+
40
+ { key => normalize_conditions(reflection.klass.name.constantize, value) }
41
+ end
42
+
43
+ def normalizable_association?(reflection)
44
+ reflection.options[:through].present? && !reflection.options[:source_type].present?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class DefaultAdapter < AbstractAdapter
6
+ # This adapter is used when no matching adapter is found
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../sti_detector'
2
+
3
+ # this class is responsible for detecting sti classes and creating new rules for the
4
+ # relevant subclasses, using the inheritance_column as a merger
5
+ module CanCan
6
+ module ModelAdapters
7
+ class StiNormalizer
8
+ class << self
9
+ def normalize(rules)
10
+ rules_cache = []
11
+ return unless defined?(ActiveRecord::Base)
12
+
13
+ rules.delete_if do |rule|
14
+ subjects = rule.subjects.select do |subject|
15
+ update_rule(subject, rule, rules_cache)
16
+ end
17
+ subjects.length == rule.subjects.length
18
+ end
19
+ rules_cache.each { |rule| rules.push(rule) }
20
+ end
21
+
22
+ private
23
+
24
+ def update_rule(subject, rule, rules_cache)
25
+ return false unless StiDetector.sti_class?(subject)
26
+
27
+ rules_cache.push(build_rule_for_subclass(rule, subject))
28
+ true
29
+ end
30
+
31
+ # create a new rule for the subclasses that links on the inheritance_column
32
+ def build_rule_for_subclass(rule, subject)
33
+ sti_conditions = { subject.inheritance_column => subject.sti_name }
34
+ new_rule_conditions =
35
+ if rule.with_scope?
36
+ rule.conditions.where(sti_conditions)
37
+ else
38
+ rule.conditions.merge(sti_conditions)
39
+ end
40
+
41
+ CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass,
42
+ new_rule_conditions, rule.block)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Base
5
+ attr_reader :adapter, :relation, :where_conditions
6
+
7
+ delegate(
8
+ :compressed_rules,
9
+ :extract_multiple_conditions,
10
+ :joins,
11
+ :model_class,
12
+ :quoted_primary_key,
13
+ :quoted_aliased_table_name,
14
+ :quoted_table_name,
15
+ to: :adapter
16
+ )
17
+ delegate :connection, :quoted_primary_key, to: :model_class
18
+ delegate :quote_table_name, to: :connection
19
+
20
+ def initialize(adapter:, relation:, where_conditions:)
21
+ @adapter = adapter
22
+ @relation = relation
23
+ @where_conditions = where_conditions
24
+ end
25
+
26
+ def aliased_table_name
27
+ @aliased_table_name ||= "#{model_class.table_name}_alias"
28
+ end
29
+
30
+ def quoted_aliased_table_name
31
+ @quoted_aliased_table_name ||= quote_table_name(aliased_table_name)
32
+ end
33
+
34
+ def quoted_table_name
35
+ @quoted_table_name ||= quote_table_name(model_class.table_name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: false
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasEachRuleAsExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where(double_exists_sql)
14
+ end
15
+
16
+ def double_exists_sql
17
+ double_exists_sql = ''
18
+
19
+ compressed_rules.each_with_index do |rule, index|
20
+ double_exists_sql << ' OR ' if index.positive?
21
+ double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})"
22
+ end
23
+
24
+ double_exists_sql
25
+ end
26
+
27
+ def sub_query_for_rule(rule)
28
+ conditions_extractor = ConditionsExtractor.new(model_class)
29
+ rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule])
30
+ joins_hash, left_joins_hash = extract_joins_from_rule(rule)
31
+ sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
32
+ end
33
+
34
+ def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
35
+ model_class
36
+ .select('1')
37
+ .joins(joins_hash)
38
+ .left_joins(left_joins_hash)
39
+ .where(
40
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
41
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
42
+ )
43
+ .where(rule_where_conditions)
44
+ .limit(1)
45
+ end
46
+
47
+ def extract_joins_from_rule(rule)
48
+ joins = {}
49
+ left_joins = {}
50
+
51
+ extra_joins_recursive([], rule.conditions, joins, left_joins)
52
+ [joins, left_joins]
53
+ end
54
+
55
+ def extra_joins_recursive(current_path, conditions, joins, left_joins)
56
+ conditions.each do |key, value|
57
+ if value.is_a?(Hash)
58
+ current_path << key
59
+ extra_joins_recursive(current_path, value, joins, left_joins)
60
+ current_path.pop
61
+ else
62
+ extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
63
+ end
64
+ end
65
+ end
66
+
67
+ def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
68
+ hash_joins = current_path_to_hash(current_path)
69
+
70
+ if value.nil?
71
+ left_joins.deep_merge!(hash_joins)
72
+ else
73
+ joins.deep_merge!(hash_joins)
74
+ end
75
+ end
76
+
77
+ # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}}
78
+ def current_path_to_hash(current_path)
79
+ hash_joins = {}
80
+ current_hash_joins = hash_joins
81
+
82
+ current_path.each do |path_part|
83
+ new_hash = {}
84
+ current_hash_joins[path_part] = new_hash
85
+ current_hash_joins = new_hash
86
+ end
87
+
88
+ hash_joins
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})")
14
+ end
15
+
16
+ def joined_alias_exists_subquery_inner_query
17
+ model_class
18
+ .unscoped
19
+ .select('1')
20
+ .left_joins(joins)
21
+ .where(*where_conditions)
22
+ .where(
23
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
24
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
25
+ )
26
+ .limit(1)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class LeftJoin < Base
5
+ def execute!
6
+ relation.left_joins(joins).distinct
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Subquery < Base
5
+ def execute!
6
+ build_joins_relation_subquery(where_conditions)
7
+ end
8
+
9
+ def build_joins_relation_subquery(where_conditions)
10
+ inner = model_class.unscoped do
11
+ model_class.left_joins(joins).where(*where_conditions)
12
+ end
13
+ model_class.where(model_class.primary_key => inner)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end