active_any 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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