activerecord-hierarchical_query 0.0.1 → 0.0.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: 031176cf523c47f50c3ca2a10b7c79129766b1e2
4
- data.tar.gz: 9abc865a1f8e2bcbc42f084611b1d588ab7f8879
3
+ metadata.gz: e317315d0b567e0880277e328380478df97fecd7
4
+ data.tar.gz: f1918a2d4c9a9e8a84f59b7af8fec42d6fb57dfa
5
5
  SHA512:
6
- metadata.gz: e5a632144805142246f4ba00cdf938b785660e5bac28063194782efbd446d901fc9154c79c8ee0c039d2d61e7afe4b10a58ff62a84115941653f5943cc137059
7
- data.tar.gz: 7931d4bf31d74b4759ab93d779efdac40ef0cdb10f3b1d583bc9120a48395eeb5c9409ee48790ff626303bf47a4129d924df98c7b2c0613f9b638696e4b5c5ea
6
+ metadata.gz: aad9047098ff944791fd13f5fed4a120e39195d3f9f4d08a17f5e734dfc8e7409e2a034acec7e0200df336c4a530003ebca1d68c2a494fac9629db3d253ace5f
7
+ data.tar.gz: 890c7ebfff9df375347f00c061ed213167fe8e09da1cf567996afc9ca5417f264215c270e987a9e8a16925284d0f9cbafc842c2f26d8c8fd142dfd29761e88d9
data/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![Code Climate](https://codeclimate.com/github/take-five/activerecord-hierarchical_query.png)](https://codeclimate.com/github/take-five/activerecord-hierarchical_query)
5
5
  [![Coverage Status](https://coveralls.io/repos/take-five/activerecord-hierarchical_query/badge.png)](https://coveralls.io/r/take-five/activerecord-hierarchical_query)
6
6
  [![Dependency Status](https://gemnasium.com/take-five/activerecord-hierarchical_query.png)](https://gemnasium.com/take-five/activerecord-hierarchical_query)
7
+ [![Gem Version](https://badge.fury.io/rb/activerecord-hierarchical_query.png)](http://badge.fury.io/rb/activerecord-hierarchical_query)
7
8
 
8
9
  Create hierarchical queries using simple DSL, recursively traverse trees using single SQL query.
9
10
 
@@ -1,20 +1,34 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/core_ext/string/inflections'
5
+
3
6
  module ActiveRecord
4
7
  module HierarchicalQuery
5
8
  module Adapters
6
9
  SUPPORTED_ADAPTERS = %w(PostgreSQL)
7
10
 
8
- autoload :PostgreSQL, 'active_record/hierarchical_query/adapters/postgresql'
11
+ ADAPTERS = Hash[
12
+ :PostgreSQL => :PostgreSQL,
13
+ :PostGIS => :PostgreSQL,
14
+ :OracleEnhanced => :Oracle
15
+ ].stringify_keys
16
+
17
+ def self.autoload(name, path = name.to_s.underscore)
18
+ super name, "active_record/hierarchical_query/adapters/#{path}"
19
+ end
20
+
21
+ autoload :PostgreSQL, 'postgresql'
22
+ autoload :Oracle
9
23
 
10
24
  def self.lookup(klass)
11
25
  name = klass.connection.adapter_name
12
26
 
13
- raise 'Your database does not support recursive queries' unless
14
- SUPPORTED_ADAPTERS.include?(name)
27
+ raise 'Your database %s does not support recursive queries' % name unless
28
+ ADAPTERS.key?(name)
15
29
 
16
- const_get(name)
30
+ const_get(ADAPTERS[name])
17
31
  end
18
32
  end # module Adapters
19
33
  end # module HierarchicalQuery
20
- end # module ActiveRecord
34
+ end # module ActiveRecord
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ require 'active_record/hierarchical_query/visitors/visitor'
4
+
5
+ module ActiveRecord
6
+ module HierarchicalQuery
7
+ module Adapters
8
+ # @api private
9
+ class Abstract
10
+ attr_reader :query,
11
+ :table
12
+
13
+ delegate :klass, :to => :query
14
+
15
+ class << self
16
+ def visitors(*visitors_classes)
17
+ define_method :visitors do
18
+ @visitors ||= visitors_classes.map { |klass| klass.new(@query) }
19
+ end
20
+ end
21
+ end
22
+
23
+ # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
24
+ def initialize(query)
25
+ @query = query
26
+ @table = klass.arel_table
27
+ end
28
+
29
+ # @example
30
+ # visit(:recursive_term, recursive_term.arel)
31
+ def visit(kind, object)
32
+ method_name = "visit_#{kind}"
33
+
34
+ visitors.reduce(object) do |*, visitor|
35
+ visitor.send(method_name, object) if visitor.respond_to?(method_name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,28 +1,18 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'active_record/hierarchical_query/cte/query'
3
+ require 'arel/nodes/postgresql'
4
+
5
+ require 'active_record/hierarchical_query/adapters/abstract'
6
+ require 'active_record/hierarchical_query/visitors/postgresql/cycle_detector'
7
+ require 'active_record/hierarchical_query/visitors/postgresql/orderings'
4
8
 
5
9
  module ActiveRecord
6
10
  module HierarchicalQuery
7
11
  module Adapters
8
12
  # @api private
9
- class PostgreSQL
10
- attr_reader :builder,
11
- :table
12
-
13
- delegate :klass, :to => :builder
14
- delegate :build_join, :to => :@query
15
-
16
- # @param [ActiveRecord::HierarchicalQuery::Builder] builder
17
- def initialize(builder)
18
- @builder = builder
19
- @table = klass.arel_table
20
- @query = CTE::Query.new(builder)
21
- end
22
-
23
- def prior
24
- @query.recursive_table
25
- end
13
+ class PostgreSQL < Abstract
14
+ visitors Visitors::PostgreSQL::CycleDetector,
15
+ Visitors::PostgreSQL::Orderings
26
16
  end # class PostgreSQL
27
17
  end # module Adapters
28
18
  end # module HierarchicalQuery
@@ -2,7 +2,8 @@
2
2
 
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
 
5
- require 'active_record/hierarchical_query/adapters'
5
+ require 'active_record/hierarchical_query/cte/query'
6
+ require 'active_record/hierarchical_query/join_builder'
6
7
 
7
8
  module ActiveRecord
8
9
  module HierarchicalQuery
@@ -22,7 +23,7 @@ module ActiveRecord
22
23
 
23
24
  def initialize(klass)
24
25
  @klass = klass
25
- @adapter = Adapters.lookup(@klass).new(self)
26
+ @query = CTE::Query.new(self)
26
27
 
27
28
  @start_with_value = nil
28
29
  @connect_by_value = nil
@@ -239,7 +240,7 @@ module ActiveRecord
239
240
  #
240
241
  # @return [Arel::Table]
241
242
  def prior
242
- @adapter.prior
243
+ @query.recursive_table
243
244
  end
244
245
  alias_method :previous, :prior
245
246
 
@@ -269,7 +270,7 @@ module ActiveRecord
269
270
 
270
271
  table_alias = join_options.fetch(:as, "#{table.name}__recursive")
271
272
 
272
- @adapter.build_join(relation, table_alias)
273
+ JoinBuilder.new(@query, relation, table_alias).build
273
274
  end
274
275
 
275
276
  private
@@ -1,15 +1,13 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'arel/nodes/postgresql'
4
-
5
3
  module ActiveRecord
6
4
  module HierarchicalQuery
7
5
  module CTE
8
6
  class NonRecursiveTerm
9
- DISALLOWED_CLAUSES = :order, :limit, :offset
7
+ DISALLOWED_CLAUSES = :order, :limit, :offset, :group, :having
10
8
 
11
9
  attr_reader :query
12
- delegate :builder, :orderings, :cycle_detector, :to => :query
10
+ delegate :builder, :adapter, :to => :query
13
11
  delegate :start_with_value, :klass, :to => :builder
14
12
 
15
13
  # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
@@ -19,25 +17,16 @@ module ActiveRecord
19
17
 
20
18
  def arel
21
19
  arel = scope.select(query.columns)
22
- .select(ordering_column)
23
20
  .except(*DISALLOWED_CLAUSES)
24
21
  .arel
25
22
 
26
- cycle_detector.visit_non_recursive(arel)
23
+ adapter.visit(:non_recursive, arel)
27
24
  end
28
25
 
29
26
  private
30
27
  def scope
31
28
  start_with_value || klass.__send__(HierarchicalQuery::DELEGATOR_SCOPE)
32
29
  end
33
-
34
- def ordering_column
35
- if orderings.any?
36
- Arel::Nodes::PostgresArray.new([orderings.row_number_expression]).as(orderings.column_name)
37
- else
38
- []
39
- end
40
- end
41
30
  end # class NonRecursiveTerm
42
31
  end
43
32
  end
@@ -1,37 +1,30 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'active_record/hierarchical_query/adapters'
3
4
  require 'active_record/hierarchical_query/cte/columns'
4
- require 'active_record/hierarchical_query/cte/cycle_detector'
5
- require 'active_record/hierarchical_query/cte/join_builder'
6
- require 'active_record/hierarchical_query/cte/orderings'
7
5
  require 'active_record/hierarchical_query/cte/union_term'
8
6
 
9
7
  module ActiveRecord
10
8
  module HierarchicalQuery
11
9
  module CTE
10
+ # CTE query builder
12
11
  class Query
13
12
  attr_reader :builder,
14
- :columns,
15
- :orderings,
16
- :cycle_detector
13
+ :adapter,
14
+ :columns
17
15
 
18
16
  delegate :klass, :table, :to => :builder
19
17
 
20
18
  # @param [ActiveRecord::HierarchicalQuery::Builder] builder
21
19
  def initialize(builder)
22
20
  @builder = builder
23
- @orderings = Orderings.new(builder)
21
+ @adapter = Adapters.lookup(klass).new(self)
24
22
  @columns = Columns.new(self)
25
- @cycle_detector = CycleDetector.new(self)
26
- end
27
-
28
- def build_join(relation, subquery_alias)
29
- JoinBuilder.new(self, relation, subquery_alias).build
30
23
  end
31
24
 
32
25
  # @return [Arel::SelectManager]
33
26
  def arel
34
- apply_ordering { build_arel }
27
+ adapter.visit(:cte, build_arel)
35
28
  end
36
29
 
37
30
  # @return [Arel::Table]
@@ -43,10 +36,6 @@ module ActiveRecord
43
36
  builder.connect_by_value[recursive_table, table]
44
37
  end
45
38
 
46
- def order_clause
47
- recursive_table[orderings.column_name].asc
48
- end
49
-
50
39
  private
51
40
  # "categories__recursive" AS (
52
41
  # SELECT ... FROM "categories"
@@ -62,16 +51,6 @@ module ActiveRecord
62
51
  UnionTerm.new(self)
63
52
  end
64
53
 
65
- def apply_ordering
66
- arel = yield
67
-
68
- if should_order?
69
- apply_ordering_to(arel)
70
- else
71
- arel
72
- end
73
- end
74
-
75
54
  def build_arel
76
55
  Arel::SelectManager.new(table.engine).
77
56
  with(:recursive, with_query).
@@ -80,14 +59,6 @@ module ActiveRecord
80
59
  take(builder.limit_value).
81
60
  skip(builder.offset_value)
82
61
  end
83
-
84
- def should_order?
85
- orderings.any? && builder.limit_value || builder.offset_value
86
- end
87
-
88
- def apply_ordering_to(select_manager)
89
- select_manager.order(order_clause)
90
- end
91
62
  end
92
63
  end
93
64
  end
@@ -5,10 +5,9 @@ module ActiveRecord
5
5
  # @return [ActiveRecord::HierarchicalQuery::CTE::Query]
6
6
  attr_reader :query
7
7
 
8
- delegate :orderings,
9
- :cycle_detector,
10
- :recursive_table,
8
+ delegate :recursive_table,
11
9
  :join_conditions,
10
+ :adapter,
12
11
  :to => :query
13
12
 
14
13
  # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
@@ -18,29 +17,16 @@ module ActiveRecord
18
17
 
19
18
  def arel
20
19
  arel = scope.select(query.columns)
21
- .select(ordering_column)
22
20
  .arel
23
21
  .join(recursive_table).on(join_conditions)
24
22
 
25
- cycle_detector.visit_recursive(arel)
23
+ adapter.visit(:recursive, arel)
26
24
  end
27
25
 
28
26
  private
29
27
  def scope
30
28
  query.builder.child_scope_value
31
29
  end
32
-
33
- def ordering_column
34
- if orderings.any?
35
- Arel::Nodes::ArrayConcat.new(parent_ordering_column, orderings.row_number_expression)
36
- else
37
- []
38
- end
39
- end
40
-
41
- def parent_ordering_column
42
- recursive_table[orderings.column_name]
43
- end
44
30
  end
45
31
  end
46
32
  end
@@ -0,0 +1,47 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ class JoinBuilder
4
+ delegate :adapter, :to => :@query
5
+
6
+ # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
7
+ # @param [ActiveRecord::Relation] join_to
8
+ # @param [#to_s] subquery_alias
9
+ def initialize(query, join_to, subquery_alias)
10
+ @query = query
11
+ @relation = join_to
12
+ @alias = Arel::Table.new(subquery_alias, ActiveRecord::Base)
13
+ end
14
+
15
+ def build
16
+ relation = @relation.joins(inner_join.to_sql)
17
+
18
+ adapter.visit(:joined_relation, relation)
19
+ end
20
+
21
+ private
22
+ def inner_join
23
+ Arel::Nodes::InnerJoin.new(aliased_subquery, constraint)
24
+ end
25
+
26
+ def primary_key
27
+ @relation.table[@relation.klass.primary_key]
28
+ end
29
+
30
+ def foreign_key
31
+ @alias[@query.klass.primary_key]
32
+ end
33
+
34
+ def constraint
35
+ Arel::Nodes::On.new(primary_key.eq(foreign_key))
36
+ end
37
+
38
+ def subquery
39
+ Arel::Nodes::Grouping.new(@query.arel.ast)
40
+ end
41
+
42
+ def aliased_subquery
43
+ Arel::Nodes::As.new(subquery, @alias)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module HierarchicalQuery
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
5
5
  end
@@ -0,0 +1,87 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ class Orderings < Visitor
5
+ ORDERING_COLUMN_ALIAS = '__order_column'
6
+
7
+ delegate :recursive_table, :to => :query
8
+
9
+ def visit_joined_relation(relation)
10
+ visit(relation) do
11
+ relation.order(order_clause)
12
+ end
13
+ end
14
+
15
+ def visit_cte(select_manager)
16
+ if should_order_cte?
17
+ select_manager.order(order_clause)
18
+ else
19
+ select_manager
20
+ end
21
+ end
22
+
23
+ protected
24
+ def visit(object)
25
+ if orderings.any?
26
+ yield
27
+ else
28
+ object
29
+ end
30
+ end
31
+
32
+ def order_clause
33
+ recursive_table[column_name].asc
34
+ end
35
+
36
+ def should_order_cte?
37
+ orderings.any? && builder.limit_value || builder.offset_value
38
+ end
39
+
40
+ def orderings
41
+ @orderings ||= builder.order_values.each_with_object([]) do |value, orderings|
42
+ orderings.concat Array.wrap(as_orderings(value))
43
+ end
44
+ end
45
+
46
+ def column_name
47
+ ORDERING_COLUMN_ALIAS
48
+ end
49
+
50
+ def as_orderings(value)
51
+ case value
52
+ when Arel::Nodes::Ordering
53
+ value
54
+
55
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
56
+ value.asc
57
+
58
+ when Symbol
59
+ table[value].asc
60
+
61
+ when Hash
62
+ value.map { |field, dir| table[field].send(dir) }
63
+
64
+ when String
65
+ value.split(',').map do |expr|
66
+ string_as_ordering(expr)
67
+ end
68
+
69
+ else
70
+ raise 'Unknown expression in ORDER BY SIBLINGS clause'
71
+ end
72
+ end
73
+
74
+ def string_as_ordering(expr)
75
+ expr.strip!
76
+
77
+ if expr.gsub!(/\bdesc\z/i, '')
78
+ Arel.sql(expr).desc
79
+ else
80
+ expr.gsub!(/\basc\z/i, '')
81
+ Arel.sql(expr).asc
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ module PostgreSQL
5
+ class CycleDetector < Visitor
6
+ COLUMN_NAME = '__path'.freeze
7
+
8
+ def visit_non_recursive(arel)
9
+ if enabled?
10
+ arel.project Arel::Nodes::PostgresArray.new([primary_key]).as(column_name)
11
+ end
12
+
13
+ arel
14
+ end
15
+
16
+ def visit_recursive(arel)
17
+ if enabled?
18
+ arel.project Arel::Nodes::ArrayConcat.new(parent_column, primary_key)
19
+ arel.constraints << Arel::Nodes::Not.new(primary_key.eq(any(parent_column)))
20
+ end
21
+
22
+ arel
23
+ end
24
+
25
+ private
26
+ def enabled?
27
+ builder.nocycle_value
28
+ end
29
+
30
+ def column_name
31
+ COLUMN_NAME
32
+ end
33
+
34
+ def parent_column
35
+ query.recursive_table[column_name]
36
+ end
37
+
38
+ def primary_key
39
+ table[klass.primary_key]
40
+ end
41
+
42
+ def any(argument)
43
+ Arel::Nodes::NamedFunction.new('ANY', [argument])
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_record/hierarchical_query/visitors/orderings'
2
+
3
+ module ActiveRecord
4
+ module HierarchicalQuery
5
+ module Visitors
6
+ module PostgreSQL
7
+ class Orderings < Visitors::Orderings
8
+ NATURAL_SORT_TYPES = Set[
9
+ :integer, :float, :decimal,
10
+ :datetime, :timestamp, :time, :date,
11
+ :boolean, :itet, :cidr, :ltree
12
+ ]
13
+
14
+ delegate :first, :to => :orderings
15
+
16
+ def visit_non_recursive(arel)
17
+ project(arel) do
18
+ Arel::Nodes::PostgresArray.new([row_number_expression]).as(column_name)
19
+ end
20
+ end
21
+
22
+ def visit_recursive(arel)
23
+ project(arel) do
24
+ Arel::Nodes::ArrayConcat.new(recursive_table[column_name], row_number_expression)
25
+ end
26
+ end
27
+
28
+ private
29
+ def project(arel)
30
+ visit(arel) { arel.project(yield) }
31
+ end
32
+
33
+ def row_number_expression
34
+ if raw_ordering?
35
+ order_attribute
36
+ else
37
+ Arel.sql("ROW_NUMBER() OVER (ORDER BY #{orderings.map(&:to_sql).join(', ')})")
38
+ end
39
+ end
40
+
41
+ def raw_ordering?
42
+ ordered_by_attribute? &&
43
+ (column = order_column) &&
44
+ NATURAL_SORT_TYPES.include?(column.type)
45
+ end
46
+
47
+ def ordered_by_attribute?
48
+ orderings.one? && first.ascending? && order_attribute.is_a?(Arel::Attributes::Attribute)
49
+ end
50
+
51
+ def order_attribute
52
+ first.expr
53
+ end
54
+
55
+ def order_column
56
+ table = order_attribute.relation
57
+
58
+ if table.engine == ActiveRecord::Base
59
+ columns = table.engine.connection_pool.columns_hash[table.name]
60
+ else
61
+ columns = table.engine.columns_hash
62
+ end
63
+
64
+ columns[order_attribute.name.to_s]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ # @api private
5
+ class Visitor
6
+ attr_reader :query
7
+
8
+ delegate :builder, :to => :query
9
+ delegate :klass, :table, :to => :builder
10
+
11
+ def initialize(query)
12
+ @query = query
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,5 @@
1
- adapter: postgresql
2
- database: hierarchical_query_test
3
- username: postgres
4
- min_messages: ERROR
1
+ pg:
2
+ adapter: postgresql
3
+ database: hierarchical_query_test
4
+ username: postgres
5
+ min_messages: ERROR
data/spec/database.yml CHANGED
@@ -1,4 +1,13 @@
1
- adapter: postgresql
2
- database: hierarchical_query_test
3
- username: vagrant
4
- min_messages: ERROR
1
+ pg:
2
+ adapter: postgresql
3
+ database: hierarchical_query_test
4
+ username: vagrant
5
+ min_messages: ERROR
6
+
7
+ oracle:
8
+ adapter: oracle_enhanced
9
+ host: localhost
10
+ port: 1521
11
+ database: xe
12
+ username: system
13
+ password: manager
data/spec/spec_helper.rb CHANGED
@@ -2,17 +2,20 @@
2
2
  require 'pathname'
3
3
  require 'logger'
4
4
 
5
- SPEC_ROOT = Pathname.new(File.dirname(__FILE__))
5
+ ENV['TZ'] = 'UTC'
6
+ ENV['DB'] ||= 'pg'
6
7
 
7
- require 'bundler/setup'
8
+ SPEC_ROOT = Pathname.new(File.dirname(__FILE__))
8
9
 
9
- Bundler.setup(:default, ENV['TRAVIS'] ? :travis : :local)
10
+ require 'bundler'
11
+ Bundler.setup(:default, ENV['TRAVIS'] ? :travis : :local, ENV['DB'].to_sym)
10
12
 
11
13
  require 'rspec'
12
14
  require 'database_cleaner'
13
15
  require 'active_record'
14
16
 
15
- ActiveRecord::Base.establish_connection(YAML.load(SPEC_ROOT.join('database.yml').read))
17
+ ActiveRecord::Base.configurations = YAML.load(SPEC_ROOT.join('database.yml').read)
18
+ ActiveRecord::Base.establish_connection(ENV['DB'].to_sym)
16
19
  ActiveRecord::Base.logger = Logger.new(ENV['DEBUG'] ? $stderr : '/dev/null')
17
20
  ActiveRecord::Base.logger.formatter = proc do |severity, datetime, progname, msg|
18
21
  "#{datetime.strftime('%H:%M:%S.%L')}: #{msg}\n"
@@ -35,11 +38,6 @@ RSpec.configure do |config|
35
38
  # --seed 1234
36
39
  config.order = 'random'
37
40
 
38
- config.before(:suite) do
39
- DatabaseCleaner.strategy = :transaction
40
- DatabaseCleaner.clean_with(:truncation)
41
- end
42
-
43
41
  config.around(:each) do |example|
44
42
  DatabaseCleaner.start
45
43
  example.run
metadata CHANGED
@@ -1,111 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-hierarchical_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexei Mikhailov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-11 00:00:00.000000000 Z
11
+ date: 2014-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 3.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: 10.1.1
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 10.1.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 2.14.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 2.14.1
69
- - !ruby/object:Gem::Dependency
70
- name: pg
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ~>
74
- - !ruby/object:Gem::Version
75
- version: 0.17.1
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ~>
81
- - !ruby/object:Gem::Version
82
- version: 0.17.1
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: database_cleaner
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
- - - ~>
73
+ - - "~>"
88
74
  - !ruby/object:Gem::Version
89
75
  version: 1.2.0
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
- - - ~>
80
+ - - "~>"
95
81
  - !ruby/object:Gem::Version
96
82
  version: 1.2.0
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: simplecov
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
- - - ~>
87
+ - - "~>"
102
88
  - !ruby/object:Gem::Version
103
89
  version: 0.8.2
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - ~>
94
+ - - "~>"
109
95
  - !ruby/object:Gem::Version
110
96
  version: 0.8.2
111
97
  description:
@@ -115,19 +101,24 @@ executables: []
115
101
  extensions: []
116
102
  extra_rdoc_files: []
117
103
  files:
104
+ - LICENSE.txt
105
+ - README.md
118
106
  - lib/active_record/hierarchical_query.rb
119
107
  - lib/active_record/hierarchical_query/adapters.rb
108
+ - lib/active_record/hierarchical_query/adapters/abstract.rb
120
109
  - lib/active_record/hierarchical_query/adapters/postgresql.rb
121
110
  - lib/active_record/hierarchical_query/builder.rb
122
111
  - lib/active_record/hierarchical_query/cte/columns.rb
123
- - lib/active_record/hierarchical_query/cte/cycle_detector.rb
124
- - lib/active_record/hierarchical_query/cte/join_builder.rb
125
112
  - lib/active_record/hierarchical_query/cte/non_recursive_term.rb
126
- - lib/active_record/hierarchical_query/cte/orderings.rb
127
113
  - lib/active_record/hierarchical_query/cte/query.rb
128
114
  - lib/active_record/hierarchical_query/cte/recursive_term.rb
129
115
  - lib/active_record/hierarchical_query/cte/union_term.rb
116
+ - lib/active_record/hierarchical_query/join_builder.rb
130
117
  - lib/active_record/hierarchical_query/version.rb
118
+ - lib/active_record/hierarchical_query/visitors/orderings.rb
119
+ - lib/active_record/hierarchical_query/visitors/postgresql/cycle_detector.rb
120
+ - lib/active_record/hierarchical_query/visitors/postgresql/orderings.rb
121
+ - lib/active_record/hierarchical_query/visitors/visitor.rb
131
122
  - lib/arel/nodes/postgresql.rb
132
123
  - spec/active_record/hierarchical_query_spec.rb
133
124
  - spec/database.travis.yml
@@ -135,8 +126,6 @@ files:
135
126
  - spec/schema.rb
136
127
  - spec/spec_helper.rb
137
128
  - spec/support/models.rb
138
- - README.md
139
- - LICENSE.txt
140
129
  homepage: https://github.com/take-five/activerecord-hierarchical_query
141
130
  licenses:
142
131
  - MIT
@@ -147,17 +136,17 @@ require_paths:
147
136
  - lib
148
137
  required_ruby_version: !ruby/object:Gem::Requirement
149
138
  requirements:
150
- - - '>='
139
+ - - ">="
151
140
  - !ruby/object:Gem::Version
152
141
  version: '0'
153
142
  required_rubygems_version: !ruby/object:Gem::Requirement
154
143
  requirements:
155
- - - '>='
144
+ - - ">="
156
145
  - !ruby/object:Gem::Version
157
146
  version: '0'
158
147
  requirements: []
159
148
  rubyforge_project:
160
- rubygems_version: 2.1.10
149
+ rubygems_version: 2.2.2
161
150
  signing_key:
162
151
  specification_version: 4
163
152
  summary: Recursively traverse trees using a single SQL query
@@ -1,63 +0,0 @@
1
- # coding: utf-8
2
-
3
- module ActiveRecord
4
- module HierarchicalQuery
5
- module CTE
6
- class CycleDetector
7
- COLUMN_NAME = '__path'.freeze
8
-
9
- attr_reader :query
10
-
11
- delegate :builder, :to => :query
12
- delegate :klass, :table, :to => :builder
13
-
14
- # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
15
- def initialize(query)
16
- @query = query
17
- end
18
-
19
- def column_name
20
- COLUMN_NAME
21
- end
22
-
23
- def visit_non_recursive(arel)
24
- visit arel do
25
- arel.project Arel::Nodes::PostgresArray.new([primary_key]).as(column_name)
26
- end
27
- end
28
-
29
- def visit_recursive(arel)
30
- visit arel do
31
- arel.project Arel::Nodes::ArrayConcat.new(parent_column, primary_key)
32
- arel.constraints << Arel::Nodes::Not.new(primary_key.eq(any(parent_column)))
33
- end
34
- end
35
-
36
- private
37
- def enabled?
38
- builder.nocycle_value
39
- end
40
-
41
- def parent_column
42
- query.recursive_table[column_name]
43
- end
44
-
45
- def primary_key
46
- table[klass.primary_key]
47
- end
48
-
49
- def visit(object)
50
- if enabled?
51
- yield
52
- end
53
-
54
- object
55
- end
56
-
57
- def any(argument)
58
- Arel::Nodes::NamedFunction.new('ANY', [argument])
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,55 +0,0 @@
1
- module ActiveRecord
2
- module HierarchicalQuery
3
- module CTE
4
- class JoinBuilder
5
- # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
6
- # @param [ActiveRecord::Relation] join_to
7
- # @param [#to_s] subquery_alias
8
- def initialize(query, join_to, subquery_alias)
9
- @query = query
10
- @relation = join_to
11
- @alias = Arel::Table.new(subquery_alias, ActiveRecord::Base)
12
- end
13
-
14
- def build
15
- apply_ordering { @relation.joins(inner_join.to_sql) }
16
- end
17
-
18
- private
19
- def inner_join
20
- Arel::Nodes::InnerJoin.new(aliased_subquery, constraint)
21
- end
22
-
23
- def primary_key
24
- @relation.table[@relation.klass.primary_key]
25
- end
26
-
27
- def foreign_key
28
- @alias[@query.klass.primary_key]
29
- end
30
-
31
- def constraint
32
- Arel::Nodes::On.new(primary_key.eq(foreign_key))
33
- end
34
-
35
- def subquery
36
- Arel::Nodes::Grouping.new(@query.arel.ast)
37
- end
38
-
39
- def aliased_subquery
40
- Arel::Nodes::As.new(subquery, @alias)
41
- end
42
-
43
- def apply_ordering
44
- scope = yield
45
-
46
- if @query.orderings.any?
47
- scope.order(@query.order_clause)
48
- else
49
- scope
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,108 +0,0 @@
1
- require 'set'
2
- require 'active_support/core_ext/array/wrap'
3
-
4
- module ActiveRecord
5
- module HierarchicalQuery
6
- module CTE
7
- class Orderings
8
- include Enumerable
9
-
10
- ORDERING_COLUMN_ALIAS = '__order_column'
11
-
12
- NATURAL_SORT_TYPES = Set[
13
- :integer, :float, :decimal,
14
- :datetime, :timestamp, :time, :date,
15
- :boolean, :itet, :cidr, :ltree
16
- ]
17
-
18
- delegate :table, :to => :@builder
19
- delegate :each, :to => :arel_nodes
20
-
21
- # @param [ActiveRecord::HierarchicalQuery::Builder] builder
22
- def initialize(builder)
23
- @builder = builder
24
- end
25
-
26
- def arel_nodes
27
- @arel_nodes ||= @builder.order_values.each_with_object([]) do |value, orderings|
28
- orderings.concat Array.wrap(as_orderings(value))
29
- end
30
- end
31
-
32
- def row_number_expression
33
- if raw_ordering?
34
- order_attribute
35
- else
36
- Arel.sql("ROW_NUMBER() OVER (ORDER BY #{arel_nodes.map(&:to_sql).join(', ')})")
37
- end
38
- end
39
-
40
- def column_name
41
- ORDERING_COLUMN_ALIAS
42
- end
43
-
44
- private
45
- def as_orderings(value)
46
- case value
47
- when Arel::Nodes::Ordering
48
- value
49
-
50
- when Arel::Nodes::Node, Arel::Attributes::Attribute
51
- value.asc
52
-
53
- when Symbol
54
- table[value].asc
55
-
56
- when Hash
57
- value.map { |field, dir| table[field].send(dir) }
58
-
59
- when String
60
- value.split(',').map do |expr|
61
- string_as_ordering(expr)
62
- end
63
-
64
- else
65
- raise 'Unknown expression in ORDER BY SIBLINGS clause'
66
- end
67
- end
68
-
69
- def string_as_ordering(expr)
70
- expr.strip!
71
-
72
- if expr.gsub!(/\s+desc\z/i, '')
73
- Arel.sql(expr).desc
74
- else
75
- expr.gsub!(/\s+asc\z/i, '')
76
- Arel.sql(expr).asc
77
- end
78
- end
79
-
80
- def raw_ordering?
81
- ordered_by_attribute? &&
82
- (column = order_column) &&
83
- NATURAL_SORT_TYPES.include?(column.type)
84
- end
85
-
86
- def ordered_by_attribute?
87
- arel_nodes.one? && first.ascending? && order_attribute.is_a?(Arel::Attributes::Attribute)
88
- end
89
-
90
- def order_attribute
91
- first.expr
92
- end
93
-
94
- def order_column
95
- table = order_attribute.relation
96
-
97
- if table.engine == ActiveRecord::Base
98
- columns = table.engine.connection_pool.columns_hash[table.name]
99
- else
100
- columns = table.engine.columns_hash
101
- end
102
-
103
- columns[order_attribute.name.to_s]
104
- end
105
- end # class Orderings
106
- end
107
- end
108
- end