torque-postgresql 0.2.16 → 1.0.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +76 -3
  3. data/lib/torque-postgresql.rb +1 -0
  4. data/lib/torque/postgresql.rb +6 -0
  5. data/lib/torque/postgresql/adapter.rb +2 -4
  6. data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
  7. data/lib/torque/postgresql/adapter/oid.rb +12 -1
  8. data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
  9. data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
  10. data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
  11. data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
  12. data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
  13. data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
  14. data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
  15. data/lib/torque/postgresql/adapter/quoting.rb +21 -0
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
  18. data/lib/torque/postgresql/arel.rb +3 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +32 -0
  21. data/lib/torque/postgresql/arel/operations.rb +18 -0
  22. data/lib/torque/postgresql/arel/visitors.rb +28 -2
  23. data/lib/torque/postgresql/associations.rb +8 -0
  24. data/lib/torque/postgresql/associations/association.rb +30 -0
  25. data/lib/torque/postgresql/associations/association_scope.rb +116 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
  27. data/lib/torque/postgresql/associations/builder.rb +2 -0
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
  30. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +25 -0
  32. data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
  33. data/lib/torque/postgresql/attributes.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  35. data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
  36. data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
  37. data/lib/torque/postgresql/attributes/enum.rb +11 -8
  38. data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
  39. data/lib/torque/postgresql/attributes/lazy.rb +1 -1
  40. data/lib/torque/postgresql/attributes/period.rb +31 -0
  41. data/lib/torque/postgresql/attributes/type_map.rb +3 -5
  42. data/lib/torque/postgresql/autosave_association.rb +40 -0
  43. data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
  44. data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
  45. data/lib/torque/postgresql/base.rb +161 -2
  46. data/lib/torque/postgresql/config.rb +91 -9
  47. data/lib/torque/postgresql/geometry_builder.rb +92 -0
  48. data/lib/torque/postgresql/i18n.rb +1 -1
  49. data/lib/torque/postgresql/railtie.rb +18 -5
  50. data/lib/torque/postgresql/reflection.rb +21 -0
  51. data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
  52. data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
  53. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
  54. data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
  55. data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
  56. data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
  57. data/lib/torque/postgresql/relation.rb +11 -10
  58. data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -2
  60. data/lib/torque/postgresql/relation/merger.rb +11 -7
  61. data/lib/torque/postgresql/schema_cache.rb +1 -1
  62. data/lib/torque/postgresql/version.rb +1 -1
  63. data/lib/torque/range.rb +40 -0
  64. metadata +41 -9
@@ -3,7 +3,7 @@ module Torque
3
3
  module I18n
4
4
 
5
5
  # Adds extra suport to localize durations
6
- # This is a temporary solution, since 3600.seconds do not translate into
6
+ # This is a temporary solution, since 3600.seconds does not translate into
7
7
  # 1 hour
8
8
  def localize(locale, object, format = :default, options = {})
9
9
  return super unless object.is_a?(ActiveSupport::Duration)
@@ -5,23 +5,36 @@ module Torque
5
5
 
6
6
  # Get information from the running rails app
7
7
  initializer 'torque-postgresql' do |app|
8
- Torque::PostgreSQL.config.eager_load = app.config.eager_load
8
+ torque_config = Torque::PostgreSQL.config
9
+ torque_config.eager_load = app.config.eager_load
9
10
 
10
11
  # Include enum on ActiveRecord::Base so it can have the correct enum
11
12
  # initializer
12
13
  Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
14
+ Torque::PostgreSQL::Attributes::EnumSet.include_on(ActiveRecord::Base)
15
+ Torque::PostgreSQL::Attributes::Period.include_on(ActiveRecord::Base)
13
16
 
14
- # Define a method to find yet to define constants
15
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
17
+ # Setup belongs_to_many association
18
+ ActiveRecord::Base.belongs_to_many_required_by_default = torque_config.associations
19
+ .belongs_to_many_required_by_default
20
+
21
+ # Define a method to find enumaerators based on the namespace
22
+ torque_config.enum.namespace.define_singleton_method(:const_missing) do |name|
16
23
  Torque::PostgreSQL::Attributes::Enum.lookup(name)
17
24
  end
18
25
 
19
26
  # Define a helper method to get a sample value
20
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
27
+ torque_config.enum.namespace.define_singleton_method(:sample) do |name|
21
28
  Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
22
29
  end
23
- end
24
30
 
31
+ # Define the exposed constant for auxiliary statements
32
+ if torque_config.auxiliary_statement.exposed_class.present?
33
+ *ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
34
+ base = ns.present? ? Object.const_get(ns.join('::')) : Object
35
+ base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
36
+ end
37
+ end
25
38
  end
26
39
  end
27
40
  end
@@ -0,0 +1,21 @@
1
+ require_relative 'reflection/abstract_reflection'
2
+ require_relative 'reflection/association_reflection'
3
+ require_relative 'reflection/belongs_to_many_reflection'
4
+ require_relative 'reflection/has_many_reflection'
5
+ require_relative 'reflection/runtime_reflection'
6
+ require_relative 'reflection/through_reflection'
7
+
8
+ module Torque
9
+ module PostgreSQL
10
+ module Reflection
11
+
12
+ def create(macro, name, scope, options, ar)
13
+ return super unless macro.eql?(:belongs_to_many)
14
+ BelongsToManyReflection.new(name, scope, options, ar)
15
+ end
16
+
17
+ end
18
+
19
+ ::ActiveRecord::Reflection.singleton_class.prepend(Reflection)
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module AbstractReflection
5
+ AREL_ATTR = ::Arel::Attributes::Attribute
6
+
7
+ ARR_NO_CAST = 'bigint'.freeze
8
+ ARR_CAST = 'bigint[]'.freeze
9
+
10
+ # Check if the foreign key actually exists
11
+ def connected_through_array?
12
+ false
13
+ end
14
+
15
+ # Monkey patching for rais 5.0
16
+ def torque_join_keys
17
+ method(:join_keys).arity.eql?(0) ? join_keys : join_keys(klass)
18
+ end
19
+
20
+ # Manually build the join constraint
21
+ def build_join_constraint(table, foreign_table)
22
+ klass_attr = table[torque_join_keys.key]
23
+ source_attr = foreign_table[torque_join_keys.foreign_key]
24
+
25
+ result = build_id_constraint(klass_attr, source_attr)
26
+ result = table.create_and([result, klass.send(:type_condition, table)]) \
27
+ if klass.finder_needs_type_condition?
28
+
29
+ result
30
+ end
31
+
32
+ # Build the id constraint checking if both types are perfect matching
33
+ def build_id_constraint(klass_attr, source_attr)
34
+ return klass_attr.eq(source_attr) unless connected_through_array?
35
+
36
+ # Klass and key are associated with the reflection Class
37
+ klass_type = klass.columns_hash[torque_join_keys.key]
38
+ # active_record and foreign_key are associated with the source Class
39
+ source_type = active_record.columns_hash[torque_join_keys.foreign_key]
40
+
41
+ # If both are attributes but the left side is not an array, and the
42
+ # right side is, use the ANY operation
43
+ any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
44
+ return klass_attr.eq(any_operation) if any_operation
45
+
46
+ # If the left side is not an array, just use the IN condition
47
+ return klass_attr.in(source_attr) unless klass_type.try(:array)
48
+
49
+ # Decide if should apply a cast to ensure same type comparision
50
+ should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer)
51
+ should_cast &= !klass_type.sql_type.eql?(source_type.sql_type)
52
+ should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR))
53
+
54
+ # Apply necessary transformations to values
55
+ klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast)
56
+ source_attr = cast_constraint_to_array(source_type, source_attr, should_cast)
57
+
58
+ # Return the overlap condition
59
+ klass_attr.overlaps(source_attr)
60
+ end
61
+
62
+ private
63
+
64
+ # Prepare a value for an array constraint overlap condition
65
+ def cast_constraint_to_array(type, value, should_cast)
66
+ base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
67
+ return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
68
+
69
+ value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
70
+ value = value.cast(ARR_CAST) if should_cast
71
+ value
72
+ end
73
+
74
+ # Check if it's possible to turn both attributes into an ANY condition
75
+ def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
76
+ return unless !klass_type.try(:array) && source_type.try(:array) &&
77
+ klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
78
+
79
+ ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
80
+ end
81
+
82
+ # returns either +nil+ or the inverse association name that it finds.
83
+ def automatic_inverse_of
84
+ return super unless connected_through_array?
85
+ if can_find_inverse_of_automatically?(self)
86
+ inverse_name = options[:as] || active_record.name.demodulize
87
+ inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
88
+ inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
89
+ inverse_name = inverse_name.to_sym
90
+
91
+ begin
92
+ reflection = klass._reflect_on_association(inverse_name)
93
+ rescue NameError
94
+ # Give up: we couldn't compute the klass type so we won't be able
95
+ # to find any associations either.
96
+ reflection = false
97
+ end
98
+
99
+ return inverse_name if reflection.connected_through_array? &&
100
+ valid_inverse_reflection?(reflection)
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,30 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module AssociationReflection
5
+
6
+ def initialize(name, scope, options, active_record)
7
+ super
8
+
9
+ raise ArgumentError, <<-MSG.squish if options[:array] && options[:polymorphic]
10
+ Associations can't be connected through an array at the same time they are
11
+ polymorphic. Please choose one of the options.
12
+ MSG
13
+ end
14
+
15
+ private
16
+
17
+ # Check if the foreign key should be pluralized
18
+ def derive_foreign_key
19
+ result = super
20
+ result = ActiveSupport::Inflector.pluralize(result) \
21
+ if collection? && connected_through_array?
22
+ result
23
+ end
24
+
25
+ end
26
+
27
+ ::ActiveRecord::Reflection::AssociationReflection.prepend(AssociationReflection)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ class BelongsToManyReflection < ::ActiveRecord::Reflection::AssociationReflection
5
+ def macro
6
+ :belongs_to_many
7
+ end
8
+
9
+ def connected_through_array?
10
+ true
11
+ end
12
+
13
+ def collection?
14
+ true
15
+ end
16
+
17
+ def association_class
18
+ Associations::BelongsToManyAssociation
19
+ end
20
+
21
+ def association_foreign_key
22
+ @association_foreign_key ||= foreign_key
23
+ end
24
+
25
+ def active_record_primary_key
26
+ @active_record_primary_key ||= options[:primary_key] || derive_primary_key
27
+ end
28
+
29
+ private
30
+
31
+ def derive_foreign_key
32
+ klass.primary_key
33
+ end
34
+
35
+ def derive_primary_key
36
+ ActiveSupport::Inflector.pluralize(klass.name.foreign_key)
37
+ end
38
+ end
39
+
40
+ ::ActiveRecord::Reflection::AssociationReflection::VALID_AUTOMATIC_INVERSE_MACROS.push(:belongs_to_many)
41
+ ::ActiveRecord::Reflection.const_set(:BelongsToManyReflection, BelongsToManyReflection)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module HasManyReflection
5
+ def connected_through_array?
6
+ options[:array]
7
+ end
8
+ end
9
+
10
+ ::ActiveRecord::Reflection::HasManyReflection.include(HasManyReflection)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module RuntimeReflection
5
+ delegate :klass, :active_record, :connected_through_array?, :macro, :name,
6
+ :build_id_constraint, to: :@reflection
7
+ end
8
+
9
+ ::ActiveRecord::Reflection::RuntimeReflection.include(RuntimeReflection)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module ThroughReflection
5
+ delegate :build_id_constraint, :connected_through_array?, to: :source_reflection
6
+ end
7
+
8
+ ::ActiveRecord::Reflection::ThroughReflection.include(ThroughReflection)
9
+ end
10
+ end
11
+ end
@@ -14,9 +14,14 @@ module Torque
14
14
  include Inheritance
15
15
 
16
16
  SINGLE_VALUE_METHODS = [:itself_only]
17
- MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records]
17
+ MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records, :select_extra]
18
18
  VALUE_METHODS = SINGLE_VALUE_METHODS + MULTI_VALUE_METHODS
19
19
 
20
+ # :nodoc:
21
+ def select_extra_values; get_value(:select_extra); end
22
+ # :nodoc:
23
+ def select_extra_values=(value); set_value(:select_extra, value); end
24
+
20
25
  # Resolve column name when calculating models, allowing the column name to
21
26
  # be more complex while keeping the query selection quality
22
27
  def calculate(operation, column_name)
@@ -41,7 +46,7 @@ module Torque
41
46
  Array.wrap(list).map do |item|
42
47
  case item
43
48
  when String
44
- ::Arel::Nodes::SqlLiteral.new(klass.send(:sanitize_sql, item.to_s))
49
+ ::Arel.sql(klass.send(:sanitize_sql, item.to_s))
45
50
  when Symbol
46
51
  base ? base.arel_attribute(item) : klass.arel_attribute(item)
47
52
  when Array
@@ -72,13 +77,9 @@ module Torque
72
77
 
73
78
  private
74
79
 
75
- def dynamic_selection
76
- @dynamic_selection ||= []
77
- end
78
-
79
80
  def build_arel(*)
80
81
  arel = super
81
- arel.project(*dynamic_selection) if select_values.blank? && dynamic_selection.any?
82
+ arel.project(*select_extra_values) if select_values.blank?
82
83
  arel
83
84
  end
84
85
 
@@ -134,9 +135,9 @@ module Torque
134
135
  warn_level = $VERBOSE
135
136
  $VERBOSE = nil
136
137
 
137
- ActiveRecord::Relation::SINGLE_VALUE_METHODS += Relation::SINGLE_VALUE_METHODS
138
- ActiveRecord::Relation::MULTI_VALUE_METHODS += Relation::MULTI_VALUE_METHODS
139
- ActiveRecord::Relation::VALUE_METHODS += Relation::VALUE_METHODS
138
+ ActiveRecord::Relation::SINGLE_VALUE_METHODS += Relation::SINGLE_VALUE_METHODS
139
+ ActiveRecord::Relation::MULTI_VALUE_METHODS += Relation::MULTI_VALUE_METHODS
140
+ ActiveRecord::Relation::VALUE_METHODS += Relation::VALUE_METHODS
140
141
  ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES += [:cast_records, :itself_only,
141
142
  :distinct_on, :auxiliary_statements]
142
143
 
@@ -17,11 +17,11 @@ module Torque
17
17
  def with!(*args)
18
18
  options = args.extract_options!
19
19
  args.each do |table|
20
- instance = table.is_a?(PostgreSQL::AuxiliaryStatement) \
21
- ? table.class.new(options) \
20
+ instance = table.is_a?(Class) && table < PostgreSQL::AuxiliaryStatement \
21
+ ? table.new(options) \
22
22
  : PostgreSQL::AuxiliaryStatement.instantiate(table, self, options)
23
- instance.ensure_dependencies!(self)
24
- self.auxiliary_statements_values |= [instance]
23
+
24
+ self.auxiliary_statements_values += [instance]
25
25
  end
26
26
 
27
27
  self
@@ -47,30 +47,23 @@ module Torque
47
47
 
48
48
  # Hook arel build to add the distinct on clause
49
49
  def build_arel(*)
50
- arel = super
51
- build_auxiliary_statements(arel)
52
- arel
50
+ subqueries = build_auxiliary_statements
51
+ return super if subqueries.nil?
52
+ super.with(subqueries)
53
53
  end
54
54
 
55
55
  # Build all necessary data for auxiliary statements
56
- def build_auxiliary_statements(arel)
56
+ def build_auxiliary_statements
57
57
  return unless self.auxiliary_statements_values.present?
58
-
59
- columns = []
60
- subqueries = self.auxiliary_statements_values.map do |klass|
61
- columns << klass.columns
62
- klass.build_arel(arel, self)
58
+ self.auxiliary_statements_values.map do |klass|
59
+ klass.build(self)
63
60
  end
64
-
65
- columns.flatten!
66
- arel.with(subqueries.flatten)
67
- dynamic_selection.concat(columns) if columns.any?
68
61
  end
69
62
 
70
63
  # Throw an error showing that an auxiliary statement of the given
71
64
  # table name isn't defined
72
65
  def auxiliary_statement_error(name)
73
- raise ArgumentError, <<-MSG.strip
66
+ raise ArgumentError, <<-MSG.squish
74
67
  There's no '#{name}' auxiliary statement defined for #{self.class.name}.
75
68
  MSG
76
69
  end
@@ -76,7 +76,7 @@ module Torque
76
76
  end
77
77
 
78
78
  columns.push(build_auto_caster_marker(arel, self.cast_records_value))
79
- dynamic_selection.concat(columns) if columns.any?
79
+ self.select_extra_values += columns if columns.any?
80
80
  end
81
81
 
82
82
  # Build as many left outer join as necessary for each dependent table
@@ -106,7 +106,7 @@ module Torque
106
106
 
107
107
  table = ::Arel::Table.new(type_attribute.camelize.underscore)
108
108
  column = table[type_attribute].in(types)
109
- ::Arel::Nodes::SqlLiteral.new(column.to_sql).as(auto_cast_attribute)
109
+ ::Arel.sql(column.to_sql).as(auto_cast_attribute)
110
110
  end
111
111
 
112
112
  end
@@ -6,7 +6,7 @@ module Torque
6
6
  def merge # :nodoc:
7
7
  super
8
8
 
9
- merge_dynamic_selection
9
+ merge_select_extra
10
10
  merge_distinct_on
11
11
  merge_auxiliary_statements
12
12
  merge_inheritance
@@ -16,9 +16,10 @@ module Torque
16
16
 
17
17
  private
18
18
 
19
- # Merge dynamic selection columns
20
- def merge_dynamic_selection
21
- relation.send(:dynamic_selection).concat(other.send(:dynamic_selection))
19
+ # Merge extra select columns
20
+ def merge_select_extra
21
+ relation.select_extra_values.concat(other.select_extra_values).uniq! \
22
+ if other.select_extra_values.present?
22
23
  end
23
24
 
24
25
  # Merge distinct on columns
@@ -41,9 +42,12 @@ module Torque
41
42
 
42
43
  # Merge settings related to inheritance tables
43
44
  def merge_inheritance
44
- relation.itself_only_value = true if other.itself_only_value
45
- relation.cast_records_value.concat(other.cast_records_value).uniq! \
46
- if other.cast_records_value.present?
45
+ relation.itself_only_value = true if other.itself_only_value.present?
46
+
47
+ if other.cast_records_value.present?
48
+ relation.cast_records_value += other.cast_records_value
49
+ relation.cast_records_value.uniq!
50
+ end
47
51
  end
48
52
 
49
53
  end