active_any 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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +27 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Guardfile +10 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +92 -0
  10. data/Rakefile +12 -0
  11. data/active_any.gemspec +36 -0
  12. data/bin/console +12 -0
  13. data/bin/setup +8 -0
  14. data/lib/active_any/adapter.rb +13 -0
  15. data/lib/active_any/adapters/abstract_adapter.rb +27 -0
  16. data/lib/active_any/adapters/object_adapter.rb +74 -0
  17. data/lib/active_any/association_relation.rb +18 -0
  18. data/lib/active_any/associations/association.rb +94 -0
  19. data/lib/active_any/associations/association_scope.rb +35 -0
  20. data/lib/active_any/associations/belongs_to_association.rb +21 -0
  21. data/lib/active_any/associations/builder/association.rb +62 -0
  22. data/lib/active_any/associations/builder/belongs_to.rb +13 -0
  23. data/lib/active_any/associations/builder/has_many.rb +17 -0
  24. data/lib/active_any/associations/collection_proxy.rb +66 -0
  25. data/lib/active_any/associations/has_many_association.rb +43 -0
  26. data/lib/active_any/associations.rb +72 -0
  27. data/lib/active_any/attribute_assignment.rb +16 -0
  28. data/lib/active_any/core.rb +26 -0
  29. data/lib/active_any/finders.rb +28 -0
  30. data/lib/active_any/reflection/association_reflection.rb +88 -0
  31. data/lib/active_any/reflection/belongs_to_reflection.rb +29 -0
  32. data/lib/active_any/reflection/has_many_reflection.rb +29 -0
  33. data/lib/active_any/reflection.rb +49 -0
  34. data/lib/active_any/relation/merger.rb +72 -0
  35. data/lib/active_any/relation/order_clause.rb +60 -0
  36. data/lib/active_any/relation/where_clause.rb +73 -0
  37. data/lib/active_any/relation.rb +243 -0
  38. data/lib/active_any/version.rb +5 -0
  39. data/lib/active_any.rb +62 -0
  40. metadata +251 -0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Associations
5
+ module Builder
6
+ class BelongsTo < Association
7
+ def self.macro
8
+ :belongs_to
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Associations
5
+ module Builder
6
+ class HasMany < Association
7
+ def self.macro
8
+ :has_many
9
+ end
10
+
11
+ def self.valid_options
12
+ super + %i[primary_key]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Associations
5
+ class CollectionProxy < ActiveAny::Relation
6
+ attr_reader :association
7
+
8
+ delegate :target, :load_target, :load_target,
9
+ :size, :empty?, :include?, to: :@association
10
+
11
+ def initialize(klass, association)
12
+ @association = association
13
+ super(klass)
14
+ end
15
+
16
+ def take(limit = nil)
17
+ load_target if find_from_target?
18
+ super
19
+ end
20
+
21
+ def scope
22
+ @scope ||= @association.scope
23
+ end
24
+
25
+ def ==(other)
26
+ load_target == other
27
+ end
28
+
29
+ def to_ary
30
+ load_target.dup
31
+ end
32
+ alias to_a to_ary
33
+
34
+ def records
35
+ load_target
36
+ end
37
+
38
+ def proxy_association
39
+ @association
40
+ end
41
+
42
+ def reset
43
+ proxy_association.reset
44
+ proxy_association.reset_scope
45
+ reset_scope
46
+ end
47
+
48
+ def reload
49
+ proxy_association.reload
50
+ reset_scope
51
+ end
52
+
53
+ def reset_scope # :nodoc:
54
+ @offsets = {}
55
+ @scope = nil
56
+ self
57
+ end
58
+
59
+ private
60
+
61
+ def find_from_target?
62
+ @association.find_from_target?
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Associations
5
+ class HasManyAssociation < Association
6
+ def reader
7
+ # TODO: implement
8
+ # reload if stale_target?
9
+
10
+ @proxy ||= CollectionProxy.create(owner, self)
11
+ @proxy.reset_scope
12
+ end
13
+
14
+ def writer(_records)
15
+ raise NotImplementedError.new, 'writer is unimplemented'
16
+ end
17
+
18
+ def size
19
+ if !find_target? || loaded?
20
+ target.size
21
+ else
22
+ scope.count
23
+ end
24
+ end
25
+
26
+ def empty?
27
+ size.zero?
28
+ end
29
+
30
+ def include?(record)
31
+ if record.is_a?(klass)
32
+ target.include?(record)
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def find_target
39
+ scope.to_a
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_any/associations/collection_proxy'
4
+ require 'active_any/associations/association_scope'
5
+ require 'active_any/associations/builder/association'
6
+ require 'active_any/associations/builder/has_many'
7
+ require 'active_any/associations/builder/belongs_to'
8
+ require 'active_any/associations/association'
9
+ require 'active_any/associations/has_many_association'
10
+ require 'active_any/associations/belongs_to_association'
11
+
12
+ module ActiveAny
13
+ module Associations
14
+ extend ActiveSupport::Concern
15
+
16
+ class AssociationNotFoundError < StandardError
17
+ def initialize(record = nil, association_name = nil)
18
+ if record && association_name
19
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
20
+ else
21
+ super('Association was not found.')
22
+ end
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def has_many(name, scope = nil, options = {})
28
+ reflection = Builder::HasMany.build(self, name, scope, options)
29
+ Reflection.add_reflection self, name, reflection
30
+ end
31
+
32
+ def belongs_to(name, scope = nil, options = {})
33
+ reflection = Builder::BelongsTo.build(self, name, scope, options)
34
+ Reflection.add_reflection self, name, reflection
35
+ end
36
+ end
37
+
38
+ def initialize_dup(*)
39
+ @association_cache = {}
40
+ super
41
+ end
42
+
43
+ private
44
+
45
+ def init_internals(*)
46
+ @association_cache = {}
47
+ super
48
+ end
49
+
50
+ def association(name)
51
+ association = association_instance_get(name)
52
+
53
+ if association.nil?
54
+ reflection = self.class.reflections[name.to_s]
55
+ raise AssociationNotFoundError.new(self, name) unless reflection
56
+
57
+ association = reflection.association_class.new(self, reflection)
58
+ association_instance_set(name, association)
59
+ end
60
+
61
+ association
62
+ end
63
+
64
+ def association_instance_get(name)
65
+ @association_cache[name]
66
+ end
67
+
68
+ def association_instance_set(name, association)
69
+ @association_cache[name] = association
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module AttributeAssignment
5
+ def init_internals(attributes = {})
6
+ assign_attributes(attributes) if attributes.present?
7
+ super
8
+ end
9
+
10
+ def assign_attributes(data)
11
+ data.each do |key, value|
12
+ public_send("#{key}=", value)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Core
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :abstract_class, instance_writer: false
9
+ class_attribute :primary_key, instance_writer: false
10
+ self.abstract_class = true
11
+ self.primary_key = nil
12
+ end
13
+
14
+ class_methods do
15
+ delegate :take, :take!, :first, :last, to: :all
16
+ end
17
+
18
+ def initialize(*args)
19
+ init_internals(*args)
20
+ end
21
+
22
+ private
23
+
24
+ def init_internals(*); end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Finders
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ delegate :find_by, :limit, :where, :take, :group, :order, to: :all
9
+
10
+ def all
11
+ Relation.create(self)
12
+ end
13
+
14
+ def find_by_query(where_clause:, limit_value:, group_values:, order_clause:)
15
+ adapter.query(
16
+ where_clause: where_clause,
17
+ limit_value: limit_value,
18
+ group_values: group_values,
19
+ order_clause: order_clause
20
+ )
21
+ end
22
+
23
+ def adapter
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Reflection
5
+ class AssociationReflection
6
+ attr_reader :name, :scope, :options, :record_class
7
+
8
+ def initialize(name, scope, options, record_class)
9
+ @name = name
10
+ @record_class = record_class
11
+ @options = options
12
+ @scope = scope
13
+ end
14
+
15
+ def association_class
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def marco
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def class_name
24
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
25
+ end
26
+
27
+ def klass
28
+ @klass ||= compute_class(class_name)
29
+ end
30
+
31
+ def compute_class(name)
32
+ name.constantize
33
+ end
34
+
35
+ def collection?
36
+ false
37
+ end
38
+
39
+ def belongs_to?
40
+ false
41
+ end
42
+
43
+ JoinKeys = Struct.new(:key, :foreign_key)
44
+
45
+ def join_keys
46
+ JoinKeys.new(join_pk, join_fk)
47
+ end
48
+
49
+ def foreign_key
50
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
51
+ end
52
+
53
+ def primary_key
54
+ @primary_key ||= options[:primary_key] || primary_key_for_record_class
55
+ end
56
+
57
+ private
58
+
59
+ def join_pk
60
+ raise NotImplementedError
61
+ end
62
+
63
+ def join_fk
64
+ raise NotImplementedError
65
+ end
66
+
67
+ def primary_key_for_record_class
68
+ klass.primary_key || (raise UnknownPrimaryKey.new, klass)
69
+ end
70
+
71
+ def derive_class_name
72
+ class_name = name.to_s
73
+ class_name = class_name.singularize if collection?
74
+ class_name.camelize
75
+ end
76
+
77
+ def derive_foreign_key
78
+ if belongs_to?
79
+ "#{name}_id"
80
+ elsif options[:as]
81
+ "#{options[:as]}_id"
82
+ else
83
+ record_class.name.foreign_key
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Reflection
5
+ class BelongsToReflection < AssociationReflection
6
+ def association_class
7
+ Associations::BelongsToAssociation
8
+ end
9
+
10
+ def macro
11
+ :belongs_to
12
+ end
13
+
14
+ def belongs_to?
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ def join_pk
21
+ primary_key
22
+ end
23
+
24
+ def join_fk
25
+ foreign_key
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ module Reflection
5
+ class HasManyReflection < AssociationReflection
6
+ def association_class
7
+ Associations::HasManyAssociation
8
+ end
9
+
10
+ def macro
11
+ :has_many
12
+ end
13
+
14
+ def collection?
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ def join_pk
21
+ foreign_key
22
+ end
23
+
24
+ def join_fk
25
+ primary_key
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_any/reflection/association_reflection'
4
+ require 'active_any/reflection/has_many_reflection'
5
+ require 'active_any/reflection/belongs_to_reflection'
6
+
7
+ module ActiveAny
8
+ module Reflection
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class_attribute :reflections, instance_writer: false
13
+ self.reflections = {}
14
+ end
15
+
16
+ class UnknownPrimaryKey < StandardError
17
+ attr_reader :model
18
+
19
+ def initialize(model = nil, description = nil)
20
+ if model
21
+ message = "Unknown primary key for table #{model.table_name} in model #{model}."
22
+ message += "\n#{description}" if description
23
+ @model = model
24
+ super(message)
25
+ else
26
+ super('Unknown primary key.')
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.create(macro, name, scope, options, klass)
32
+ reflection_class =
33
+ case macro
34
+ when :belongs_to
35
+ BelongsToReflection
36
+ when :has_many
37
+ HasManyReflection
38
+ else
39
+ raise "Unsupported Macro: #{macro}"
40
+ end
41
+
42
+ reflection_class.new(name, scope, options, klass)
43
+ end
44
+
45
+ def self.add_reflection(klass, name, reflection)
46
+ klass.reflections = klass.reflections.merge(name.to_s => reflection)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ class Relation
5
+ class HashMerger
6
+ attr_reader :relation, :hash
7
+
8
+ def initialize(relation, hash)
9
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
10
+
11
+ @relation = relation
12
+ @hash = hash
13
+ end
14
+
15
+ def merge
16
+ Merger.new(relation, other).merge
17
+ end
18
+
19
+ def other
20
+ other = Relation.create(relation.klass)
21
+ hash.each { |k, v| other.public_send("#{k}!", v) }
22
+ other
23
+ end
24
+ end
25
+
26
+ class Merger
27
+ attr_reader :relation, :values, :other
28
+
29
+ def initialize(relation, other)
30
+ @relation = relation
31
+ @values = other.values
32
+ @other = other
33
+ end
34
+
35
+ def normal_values
36
+ Relation::VALUE_METHODS -
37
+ Relation::CLAUSE_METHODS -
38
+ %i[includes preload joins order reverse_order lock create_with reordering]
39
+ end
40
+
41
+ def merge
42
+ normal_values.each do |name|
43
+ value = values[name]
44
+ relation.send("#{name}!", *value) unless value.nil? || (value.blank? && value != false)
45
+ end
46
+
47
+ merge_multi_values
48
+ merge_single_values
49
+ merge_clauses
50
+
51
+ relation
52
+ end
53
+
54
+ private
55
+
56
+ def merge_multi_values
57
+ relation.order! other.order_clause unless other.order_clause.empty?
58
+ relation.group! other.group_values if other.group_values
59
+ end
60
+
61
+ def merge_single_values; end
62
+
63
+ def merge_clauses
64
+ CLAUSE_METHODS.each do |method|
65
+ clause = relation.get_value(method)
66
+ other_clause = other.get_value(method)
67
+ relation.set_value(method, clause.merge(other_clause))
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ class Relation
5
+ class OrderClause
6
+ def self.empty
7
+ new
8
+ end
9
+
10
+ OrderValue = Struct.new(:key, :sort_type)
11
+
12
+ attr_reader :order_values
13
+
14
+ def initialize(values = [], reverse = false)
15
+ @reverse = reverse
16
+ @order_values = convert_order_values(values)
17
+ end
18
+
19
+ def reverse?
20
+ @reverse
21
+ end
22
+
23
+ def reverse!
24
+ @reverse = true
25
+ self
26
+ end
27
+
28
+ def +(other)
29
+ OrderClause.new(
30
+ order_values + other.order_values,
31
+ reverse? || other.reverse?
32
+ )
33
+ end
34
+
35
+ alias merge +
36
+
37
+ def ==(other)
38
+ reverse? == other.reverse? && order_values == other.order_values
39
+ end
40
+
41
+ def empty?
42
+ order_values.empty?
43
+ end
44
+
45
+ private
46
+
47
+ def convert_order_values(values)
48
+ values.map do |arg|
49
+ case arg
50
+ when ::Hash then OrderValue.new(arg.keys.first, arg.values.first)
51
+ when ::Symbol, ::String then OrderValue.new(arg, :asc)
52
+ when OrderValue then arg
53
+ else
54
+ raise ArgumentError
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAny
4
+ class Relation
5
+ class WhereClause
6
+ class Condition
7
+ attr_reader :key, :value
8
+
9
+ def initialize(key, value)
10
+ @key = key
11
+ @value = value
12
+
13
+ register_handler(BasicObject, :base_handler)
14
+ register_handler(Regexp, :regexp_handler)
15
+ register_handler(Range, :range_handler)
16
+ register_handler(Array, :array_handler)
17
+ end
18
+
19
+ def evaluate(adapter, record)
20
+ adapter.public_send(handle_for(value), record, key, value)
21
+ end
22
+
23
+ def to_h
24
+ { key => value }
25
+ end
26
+
27
+ private
28
+
29
+ def handle_for(value)
30
+ handlers.select { |klass, _| value.is_a?(klass) }.last.last
31
+ end
32
+
33
+ def handlers
34
+ @handlers ||= []
35
+ end
36
+
37
+ def register_handler(klass, method)
38
+ handlers << [klass, method]
39
+ end
40
+ end
41
+
42
+ attr_reader :conditions
43
+
44
+ def initialize(hash = {}, conditions = nil)
45
+ @conditions = conditions ? conditions : hash.map { |key, value| Condition.new(key, value) }
46
+ end
47
+
48
+ def self.empty
49
+ new
50
+ end
51
+
52
+ def ==(other)
53
+ other.conditions.sort_by(&:key) == conditions.sort_by(&:key)
54
+ end
55
+
56
+ def +(other)
57
+ WhereClause.new({}, (conditions + other.conditions).flatten)
58
+ end
59
+
60
+ alias merge +
61
+
62
+ def merge!(hash)
63
+ hash.each do |key, value|
64
+ conditions << Condition.new(key, value)
65
+ end
66
+ end
67
+
68
+ def all?(&block)
69
+ conditions.all?(&block)
70
+ end
71
+ end
72
+ end
73
+ end