spanner-translator 0.3.1 → 0.3.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 455e11c334ebe5c0a3e917dbd84599176e4d16d5c905a0bfbd48e6b36b5975b6
4
- data.tar.gz: 246699204b50ed9bf04b4a79639e66dcbd4219ee8fffb013823ddaeda38017e9
3
+ metadata.gz: 5245a782dcce416f03a31c38710e8e5a3acb18a64d0082c44ebb004a1e8121ec
4
+ data.tar.gz: 264532835fce24060de4579d46cf95a09b777fb05b7d0c9e6f3c16aa02360ce1
5
5
  SHA512:
6
- metadata.gz: 19c691ff6b6d087bfdc489ff90ee655dbfc6fce1a44a5640b7681d11750dbef24f03a207a37554f2ad38c62050adffea47c153eec5c41b88d3c34f545247ca8b
7
- data.tar.gz: eedfb4aa235247374887463df3c116152278de8e3855d9f7d8fed870342b1a320c570b61a902bb8e367b2db7befcf0174aed7fdf08b80fa5a15cbc4cfdde0d3e
6
+ metadata.gz: 06645ad45c11413b73284d32b5fed8dc6dbb9ba1f2c2d67b78c29dae3f188dac3bace1220fbe7342990777c2c608f6f8849deabc66ef92146e5187a81594cdf9
7
+ data.tar.gz: 846b937be1eb9b5e829216567b0070790d99cef0f1c2b1b889364de6c5bc93717970c023a1658002c6c7a3f36f44cb0cee4705b3cb7dcc2a7167eb815e7e3aa7
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ require:
2
+ - rubocop-rspec
3
+
1
4
  AllCops:
2
5
  TargetRubyVersion: 3.2.2
3
6
  NewCops: enable
@@ -25,3 +28,12 @@ Metrics/BlockLength:
25
28
  Max: 120
26
29
  Exclude:
27
30
  - 'spec/**/*'
31
+
32
+ RSpec/ExampleLength:
33
+ Max: 120
34
+
35
+ RSpec/ContextWording:
36
+ Enabled: false
37
+
38
+ RSpec/MultipleMemoizedHelpers:
39
+ Enabled: false
data/Gemfile CHANGED
@@ -10,3 +10,4 @@ gem "debug", ">= 1.0.0"
10
10
  gem "rake", "~> 13.0"
11
11
  gem "rspec", "~> 3.0"
12
12
  gem "rubocop", "~> 1.59.0"
13
+ gem "rubocop-rspec"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spanner-translator (0.3.1)
4
+ spanner-translator (0.3.4)
5
5
  rubocop-ast (~> 1.30.0)
6
6
 
7
7
  GEM
@@ -59,6 +59,14 @@ GEM
59
59
  unicode-display_width (>= 2.4.0, < 3.0)
60
60
  rubocop-ast (1.30.0)
61
61
  parser (>= 3.2.1.0)
62
+ rubocop-capybara (2.20.0)
63
+ rubocop (~> 1.41)
64
+ rubocop-factory_bot (2.25.1)
65
+ rubocop (~> 1.41)
66
+ rubocop-rspec (2.26.1)
67
+ rubocop (~> 1.40)
68
+ rubocop-capybara (~> 2.17)
69
+ rubocop-factory_bot (~> 2.22)
62
70
  ruby-progressbar (1.13.0)
63
71
  stringio (3.0.8)
64
72
  unicode-display_width (2.5.0)
@@ -73,6 +81,7 @@ DEPENDENCIES
73
81
  rake (~> 13.0)
74
82
  rspec (~> 3.0)
75
83
  rubocop (~> 1.59.0)
84
+ rubocop-rspec
76
85
  spanner-translator!
77
86
 
78
87
  BUNDLED WITH
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module AST
5
5
  # Spanner-specific extensions for RuboCop::AST::SendNode objects
6
6
  class SendNode
7
- %i[index column bigint datetime timestamp].each do |t_method_name|
7
+ %i[index column bigint integer datetime timestamp timestamps].each do |t_method_name|
8
8
  define_method(:"t_#{t_method_name}?") do
9
9
  sending_t? && method_name == t_method_name
10
10
  end
@@ -31,7 +31,7 @@ module RuboCop
31
31
  find_hash_option(option_name.to_sym)&.source
32
32
  end
33
33
 
34
- %i[null name unique limit default].each do |option_name|
34
+ %i[null name unique limit default primary_key].each do |option_name|
35
35
  define_method(:"#{option_name}_option") do
36
36
  find_hash_option(option_name)
37
37
  end
@@ -10,7 +10,7 @@ module Spanner
10
10
  class << self
11
11
  # process code translation, raising errors if a CheckRule fails.
12
12
  def process_code!(code,
13
- check_classes: Rules::Check.constants,
13
+ check_classes: Rules::Check::ORDERED_CHECK_RULES,
14
14
  rule_classes: Rules::Translation::ORDERED_SCHEMA_RULES, options: {})
15
15
  check_classes.each do |rule_class|
16
16
  process_check_rule(Spanner::Translator::Rules::Check.const_get(rule_class), code)
@@ -50,14 +50,9 @@ module Spanner
50
50
  # dumb create_table block code formatting
51
51
  def formatted_create_table(code)
52
52
  code.lines.map do |l|
53
- case l
54
- when /(create_table|end)/
55
- l.strip
56
- when /t\.index/
57
- " #{l.strip}" unless Spanner::Translator.configuration.skip_indexes
58
- else
59
- " #{l.strip}"
60
- end
53
+ next if /^\s*$/ =~ l
54
+
55
+ /(create_table|end)/ =~ l ? l.strip : " #{l.strip}"
61
56
  end.compact.join("\n")
62
57
  end
63
58
  end
@@ -4,7 +4,7 @@ module Spanner
4
4
  module Translator
5
5
  # Configuration
6
6
  class Configuration
7
- attr_accessor :rules, :checks, :db_schema, :skip_indexes
7
+ attr_accessor :rules, :checks, :db_schema, :skip_indexes, :primary_key_type
8
8
  attr_reader :default_primary_keys
9
9
 
10
10
  def initialize
@@ -17,6 +17,7 @@ module Spanner
17
17
  @db_schema = "db/schema.rb"
18
18
  @default_primary_keys = ["id"]
19
19
  @skip_indexes = false
20
+ @primary_key_type = :integer
20
21
  end
21
22
 
22
23
  # Set primary key column(s), make sure it is an array of symbols
@@ -6,6 +6,8 @@ module Spanner
6
6
  module Translator
7
7
  module Rules
8
8
  module Check # rubocop:disable Style/Documentation
9
+ ORDERED_CHECK_RULES = [].freeze
10
+
9
11
  autoload :HasAnyIndex, "spanner/translator/rules/check/has_any_index"
10
12
  end
11
13
  end
@@ -4,18 +4,25 @@ module Spanner
4
4
  module Translator
5
5
  module Rules
6
6
  module Translation
7
- # Add t.time :committed_at with allow_commit_timestamp: true before the first t.index
8
- #
9
- # FIXME (@abachman): if table has no indexes, this rule will not be applied
10
- #
7
+ # In the create_table block, add t.time :committed_at with
8
+ # allow_commit_timestamp: true before the first t.index or at the end of
9
+ # the block if no t.index is found.
11
10
  class AddCommittedAtTimestamp < BaseRule
12
- def on_send(node)
13
- return if @seen["committed_at"]
14
- return unless node.t_index?
11
+ COMMITTED_AT_COLUMN = 't.time "committed_at", null: false, allow_commit_timestamp: true'
15
12
 
16
- committed_at_column = 't.time "committed_at", null: false, allow_commit_timestamp: true'
17
- @rewriter.insert_before(node.loc.expression, "#{committed_at_column}\n ")
18
- @seen["committed_at"] = true
13
+ def on_block(node)
14
+ # only pay attention to create_table blocks
15
+ return unless node.send_node.method_name == :create_table
16
+
17
+ # try all body children to see if any are t.index, if so, insert before
18
+ node.body.children.each do |child|
19
+ next unless child.is_a?(RuboCop::AST::SendNode) && child.t_index?
20
+
21
+ return @rewriter.insert_before(child.loc.expression, "#{COMMITTED_AT_COLUMN}\n ")
22
+ end
23
+
24
+ # otherwise, insert at end of block
25
+ @rewriter.insert_before(node.loc.end, " #{COMMITTED_AT_COLUMN}\n")
19
26
  end
20
27
  end
21
28
  end
@@ -6,8 +6,9 @@ module Spanner
6
6
  module Translator
7
7
  module Rules
8
8
  module Translation
9
- # Based on the `create_table` statement, add an explicit column
10
- # creation for the default primary key.
9
+ # Based on the `create_table` statement, add an explicit column creation
10
+ # for the default primary key when the default is "id" and not already
11
+ # defined.
11
12
  #
12
13
  # Scenarios:
13
14
  # create_table "users" do |t|
@@ -25,7 +26,8 @@ module Spanner
25
26
 
26
27
  primary_key_name = "id"
27
28
 
28
- @rewriter.insert_before(node.loc.expression, "t.integer \"#{primary_key_name}\", limit: 8, null: false\n ")
29
+ @rewriter.insert_before(node.loc.expression,
30
+ "t.integer \"#{primary_key_name}\", null: false # generated\n ")
29
31
  @seen["primary_key"] = true
30
32
  end
31
33
  end
@@ -14,6 +14,7 @@ module Spanner
14
14
  def on_send(node)
15
15
  capture_column_nullity(node)
16
16
  return unless node.t_index?
17
+ return @rewriter.remove(node.loc.expression) if Spanner::Translator.configuration.skip_indexes
17
18
 
18
19
  @rewriter.replace(node.loc.expression, "t.index #{new_arguments_for_column_definition(node)}")
19
20
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spanner
4
+ module Translator
5
+ module Rules
6
+ module Translation
7
+ # In the create_table block, add t.time :committed_at with
8
+ # allow_commit_timestamp: true before the first t.index or at the end of
9
+ # the block if no t.index is found.
10
+ class AddTimestamps < BaseRule
11
+ COLUMN_DEF = "t.timestamps # generated"
12
+
13
+ def on_send(node)
14
+ return unless node.t_datetime? && %w[created_at updated_at].include?(node.first_argument.value)
15
+
16
+ full_line = Parser::Source::Range.new(@rewriter.source_buffer,
17
+ node.loc.expression.begin_pos,
18
+ node.loc.expression.end_pos)
19
+ @rewriter.remove(full_line)
20
+ end
21
+
22
+ def on_block(node) # rubocop:disable Metrics/AbcSize
23
+ # only pay attention to create_table blocks
24
+ return unless node.send_node.method_name == :create_table
25
+
26
+ # check all already has `t.timestamps` bail out
27
+ return if timestamp_child?(node.body.children)
28
+
29
+ # try all body children to see if any are t.index, if so, insert before
30
+ t_index = node.body.children.find { |child| child.is_a?(RuboCop::AST::SendNode) && child.t_index? }
31
+ return @rewriter.insert_before(t_index.loc.expression, "#{COLUMN_DEF}\n ") if t_index
32
+
33
+ # otherwise, insert at end of block
34
+ @rewriter.insert_before(node.loc.end, " #{COLUMN_DEF}\n")
35
+ end
36
+
37
+ private
38
+
39
+ def timestamp_child?(children)
40
+ children.any? do |child|
41
+ child.is_a?(RuboCop::AST::SendNode) && child.t_timestamps?
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Spanner
6
+ module Translator
7
+ module Rules
8
+ module Translation
9
+ # In the create_table block, add t.time :committed_at with
10
+ # allow_commit_timestamp: true before the first t.index or at the end of
11
+ # the block if no t.index is found.
12
+ class EnsurePrimaryKeyColumnsExist < BaseRule
13
+ ID_COLUMN_TEMPLATE = %(t.%<type>s "%<colname>s", null: false # generated\n )
14
+
15
+ def on_block(node)
16
+ # only pay attention to create_table blocks
17
+ return unless node.send_node.method_name == :create_table
18
+
19
+ # get primary_key hash argument value
20
+ specified_primary_keys = value_of(node.send_node.primary_key_option.value)
21
+ return unless specified_primary_keys
22
+
23
+ # get primary_key column name[s]
24
+ undefined_columns = undefined_primary_key_columns node.body.children, specified_primary_keys
25
+
26
+ # otherwise, insert at beginning of block
27
+ undefined_columns.each do |colname|
28
+ @rewriter.insert_before(node.body.loc.expression, id_column_source(colname))
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def value_of(node)
35
+ case node
36
+ when RuboCop::AST::ArrayNode
37
+ node.children.map { |c| value_of(c) }
38
+ when RuboCop::AST::StrNode, RuboCop::AST::SymbolNode
39
+ node.value.to_s
40
+ else
41
+ raise "Unexpected node type in argument slot: #{node.class}"
42
+ end
43
+ end
44
+
45
+ def undefined_primary_key_columns(children, columns)
46
+ columns = Array(columns)
47
+
48
+ children.each do |child|
49
+ next unless string_argument_haver?(child)
50
+
51
+ colname = child.arguments.first.value.to_s
52
+ columns.delete(colname)
53
+ end
54
+
55
+ columns
56
+ end
57
+
58
+ def string_argument_haver?(child)
59
+ child.is_a?(RuboCop::AST::SendNode) &&
60
+ child.sending_t? &&
61
+ child.arguments.first.is_a?(RuboCop::AST::StrNode)
62
+ end
63
+
64
+ def id_column_source(colname)
65
+ format(ID_COLUMN_TEMPLATE, colname: colname, type: Spanner::Translator.configuration.primary_key_type)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -14,10 +14,11 @@ module Spanner
14
14
 
15
15
  constraint_name = generate_constraint_name(node)
16
16
  unless_seen(constraint_name) do
17
- @rewriter.replace(node.loc.expression, "t.string #{new_arguments_for_column_definition(node)}")
17
+ @rewriter.replace(node.loc.expression,
18
+ "t.string #{new_arguments_for_column_definition(node)} # generated")
18
19
  constraint = %["#{column_name(node)} IN (#{choices_for_enum_string(node)})"]
19
20
  @rewriter.insert_after(node.last_sibling.source_range,
20
- "\n t.check_constraint #{constraint}, name: \"#{constraint_name}\"")
21
+ "\n t.check_constraint #{constraint}, name: \"#{constraint_name}\" # generated")
21
22
  end
22
23
  end
23
24
 
@@ -13,7 +13,7 @@ module Spanner
13
13
  conditionally_propagate_argument new_arguments, node, "null"
14
14
  conditionally_propagate_argument new_arguments, node, "default"
15
15
 
16
- @rewriter.replace(node.loc.expression, "t.time #{new_arguments.join(", ")}")
16
+ @rewriter.replace(node.loc.expression, "t.time #{new_arguments.join(", ")} # generated")
17
17
  end
18
18
  end
19
19
  end
@@ -10,20 +10,24 @@ module Spanner
10
10
  ORDERED_SCHEMA_RULES = %w[
11
11
  AddExplicitPrimaryKey
12
12
  AddCommittedAtTimestamp
13
+ AddTimestamps
13
14
  AddIndexOptions
14
15
  EnumColumnToStringWithConstraint
15
16
  TimeColumnsToTime
16
17
  BigintToInteger
17
18
  ReplaceCreateTableArgs
19
+ EnsurePrimaryKeyColumnsExist
18
20
  ].freeze
19
21
 
20
22
  autoload :AddExplicitPrimaryKey, "spanner/translator/rules/translation/add_explicit_primary_key"
21
23
  autoload :AddCommittedAtTimestamp, "spanner/translator/rules/translation/add_committed_at_timestamp"
22
24
  autoload :AddIndexOptions, "spanner/translator/rules/translation/add_index_options"
23
25
  autoload :AddTestCase, "spanner/translator/rules/translation/add_test_case"
26
+ autoload :AddTimestamps, "spanner/translator/rules/translation/add_timestamps"
24
27
  autoload :BigintToInteger, "spanner/translator/rules/translation/bigint_to_integer"
25
28
  autoload :EnumColumnToStringWithConstraint,
26
29
  "spanner/translator/rules/translation/enum_column_to_string_with_constraint"
30
+ autoload :EnsurePrimaryKeyColumnsExist, "spanner/translator/rules/translation/ensure_primary_key_columns_exist"
27
31
  autoload :ReplaceCreateTableArgs, "spanner/translator/rules/translation/replace_create_table_args"
28
32
  autoload :ReplaceApplicationRecord, "spanner/translator/rules/translation/replace_application_record"
29
33
  autoload :TimeColumnsToTime, "spanner/translator/rules/translation/time_columns_to_time"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Spanner
4
4
  module Translator
5
- VERSION = "0.3.1"
5
+ VERSION = "0.3.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spanner-translator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Bachman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-10 00:00:00.000000000 Z
11
+ date: 2024-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop-ast
@@ -57,8 +57,10 @@ files:
57
57
  - lib/spanner/translator/rules/translation/add_explicit_primary_key.rb
58
58
  - lib/spanner/translator/rules/translation/add_index_options.rb
59
59
  - lib/spanner/translator/rules/translation/add_test_case.rb
60
+ - lib/spanner/translator/rules/translation/add_timestamps.rb
60
61
  - lib/spanner/translator/rules/translation/base_rule.rb
61
62
  - lib/spanner/translator/rules/translation/bigint_to_integer.rb
63
+ - lib/spanner/translator/rules/translation/ensure_primary_key_columns_exist.rb
62
64
  - lib/spanner/translator/rules/translation/enum_column_to_string_with_constraint.rb
63
65
  - lib/spanner/translator/rules/translation/replace_application_record.rb
64
66
  - lib/spanner/translator/rules/translation/replace_create_table_args.rb