Module: BetterAuth::Hanami::Migration

Defined in:
lib/better_auth/hanami/migration.rb

Class Method Summary collapse

Class Method Details

.add_column_line(logical_field, attributes, tables) ⇒ Object



151
152
153
154
155
156
157
158
159
160
# File 'lib/better_auth/hanami/migration.rb', line 151

def add_column_line(logical_field, attributes, tables)
  return foreign_key_line("add_foreign_key", logical_field, attributes, tables) if attributes[:references]

  column = attributes[:field_name] || physical_name(logical_field)
  parts = ["add_column :#{column}", hanami_type(attributes)]
  parts << "null: false" if attributes[:required]
  default = default_value(attributes)
  parts << "default: #{default}" unless default.nil?
  "      #{parts.join(", ")}"
end

.alter_table_index_lines(change) ⇒ Object



162
163
164
165
166
167
168
169
170
# File 'lib/better_auth/hanami/migration.rb', line 162

def alter_table_index_lines(change)
  unique = change.unique ? ", unique: true" : ""
  [
    "",
    "    alter_table :#{change.table_name} do",
    "      add_index :#{change.field_name}#{unique}",
    "    end"
  ]
end

.alter_table_lines(change, tables) ⇒ Object



142
143
144
145
146
147
148
149
# File 'lib/better_auth/hanami/migration.rb', line 142

def alter_table_lines(change, tables)
  lines = ["", "    alter_table :#{change.table_name} do"]
  change.fields.each do |logical_field, attributes|
    lines << add_column_line(logical_field, attributes, tables)
  end
  lines << "    end"
  lines
end

.auth_tables_for(options_or_tables) ⇒ Object



224
225
226
227
228
229
230
# File 'lib/better_auth/hanami/migration.rb', line 224

def auth_tables_for(options_or_tables)
  if options_or_tables.is_a?(Hash) && options_or_tables.values.all? { |value| value.is_a?(Hash) && value.key?(:fields) && value.key?(:model_name) }
    options_or_tables
  else
    BetterAuth::Schema.auth_tables(options_or_tables)
  end
end

.column_line(logical_field, attributes, options) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/better_auth/hanami/migration.rb', line 123

def column_line(logical_field, attributes, options)
  return foreign_key_line("foreign_key", logical_field, attributes, options) if attributes[:references]

  column = attributes[:field_name] || physical_name(logical_field)
  parts = ["column :#{column}", hanami_type(attributes)]
  parts << "null: false" if attributes[:required]
  default = default_value(attributes)
  parts << "default: #{default}" unless default.nil?
  "      #{parts.join(", ")}"
end

.create_table_lines(table, options) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/better_auth/hanami/migration.rb', line 108

def create_table_lines(table, options)
  table_name = table.fetch(:model_name)
  lines = ["", "    create_table :#{table_name} do"]
  table.fetch(:fields).each do |logical_field, attributes|
    lines << column_line(logical_field, attributes, options)
  end
  lines << "      primary_key [:id]" if table.fetch(:fields).key?("id")
  table.fetch(:fields).each do |logical_field, attributes|
    index = index_line(logical_field, attributes)
    lines << index if index
  end
  lines << "    end"
  lines
end

.current_schema(connection) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/better_auth/hanami/migration.rb', line 82

def current_schema(connection)
  connection.tables.each_with_object({}) do |table_name, schema|
    columns = connection.schema(table_name).each_with_object({}) do |entry, result|
      column,  = entry
      result[column.to_s] = ([:db_type] || [:type]).to_s
    end
    indexes = {names: Set.new, columns: Set.new, unique_columns: Set.new}
    connection.indexes(table_name).each do |name, |
      indexes[:names] << name.to_s
      Array([:columns]).each do |column|
        column = column.to_s
        indexes[:columns] << column
        indexes[:unique_columns] << column if [:unique]
      end
    end
    schema[table_name.to_s] = {name: table_name.to_s, columns: columns, indexes: indexes}
  end
end

.default_hanami_database?(database) ⇒ Boolean

Returns:

  • (Boolean)


101
102
103
104
105
106
# File 'lib/better_auth/hanami/migration.rb', line 101

def default_hanami_database?(database)
  return false unless database.respond_to?(:source_location)

  path, = database.source_location
  path.to_s.end_with?("better_auth/hanami/configuration.rb")
end

.default_value(attributes) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/better_auth/hanami/migration.rb', line 182

def default_value(attributes)
  default = attributes[:default_value]
  return if default.respond_to?(:call)

  case default
  when true then "true"
  when false then "false"
  when Numeric then default.to_s
  when String then default.inspect
  end
end

.foreign_key_line(command, logical_field, attributes, options_or_tables) ⇒ Object



194
195
196
197
198
199
200
201
202
203
# File 'lib/better_auth/hanami/migration.rb', line 194

def foreign_key_line(command, logical_field, attributes, options_or_tables)
  column = attributes[:field_name] || physical_name(logical_field)
  reference = attributes.fetch(:references)
  target, target_key = foreign_key_target(reference, options_or_tables)
  parts = ["#{command} :#{column}, :#{target}", "type: #{hanami_type(attributes)}"]
  parts << "null: false" if attributes[:required]
  parts << "key: :#{target_key}" unless target_key == "id"
  parts << "on_delete: :#{reference[:on_delete]}" if reference[:on_delete]
  "      #{parts.join(", ")}"
end

.foreign_key_target(reference, options_or_tables) ⇒ Object



205
206
207
208
209
210
211
# File 'lib/better_auth/hanami/migration.rb', line 205

def foreign_key_target(reference, options_or_tables)
  tables = auth_tables_for(options_or_tables)
  model = reference.fetch(:model).to_s
  table = tables.fetch(model, nil) || tables.each_value.find { |candidate| candidate.fetch(:model_name).to_s == model }
  target = table&.fetch(:model_name) || model
  [target, foreign_key_target_field(reference, table)]
end

.foreign_key_target_field(reference, table) ⇒ Object



213
214
215
216
217
218
219
220
221
222
# File 'lib/better_auth/hanami/migration.rb', line 213

def foreign_key_target_field(reference, table)
  field = reference.fetch(:field).to_s
  return physical_name(field) unless table

  attributes = table.fetch(:fields).fetch(field, nil)
  return attributes[:field_name] || physical_name(field) if attributes
  return field if table.fetch(:fields).each_value.any? { |data| data[:field_name].to_s == field }

  physical_name(field)
end

.hanami_type(attributes) ⇒ Object



172
173
174
175
176
177
178
179
180
# File 'lib/better_auth/hanami/migration.rb', line 172

def hanami_type(attributes)
  case attributes[:type]
  when "boolean" then "TrueClass"
  when "date" then "DateTime"
  when "number" then attributes[:bigint] ? ":Bignum" : "Integer"
  when "json", "string[]", "number[]" then "JSON"
  else "String"
  end
end

.index_line(logical_field, attributes) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/better_auth/hanami/migration.rb', line 134

def index_line(logical_field, attributes)
  return unless attributes[:unique] || attributes[:index]

  column = attributes[:field_name] || physical_name(logical_field)
  unique = attributes[:unique] ? ", unique: true" : ""
  "      index :#{column}#{unique}"
end

.physical_name(value) ⇒ Object



232
233
234
235
236
237
# File 'lib/better_auth/hanami/migration.rb', line 232

def physical_name(value)
  value.to_s
    .gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
    .tr("-", "_")
    .downcase
end

.plan_pending(options) ⇒ Object

Raises:

  • (BetterAuth::SQLMigration::UnsupportedAdapterError)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/better_auth/hanami/migration.rb', line 46

def plan_pending(options)
  config = BetterAuth::SQLMigration.configuration_for(options)
  if config.database == :memory
    raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection"
  end
  if default_hanami_database?(config.database) && !(defined?(::Hanami) && ::Hanami.respond_to?(:app))
    raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection"
  end
  auth = BetterAuth.auth(config.to_h)
  adapter = auth.context.adapter
  connection = adapter.connection if adapter.respond_to?(:connection)
  raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection" unless connection

  BetterAuth::SQLMigration.plan_from_existing(
    config,
    existing: current_schema(connection),
    dialect: sequel_dialect(connection)
  )
end

.render(options) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/better_auth/hanami/migration.rb', line 10

def render(options)
  tables = BetterAuth::Schema.auth_tables(options)
  lines = [
    "# frozen_string_literal: true",
    "",
    "require \"date\"",
    "require \"rom-sql\"",
    "",
    "ROM::SQL.migration do",
    "  change do"
  ]
  tables.each_value { |table| lines.concat(create_table_lines(table, options)) }
  lines.concat(["  end", "end", ""])
  lines.join("\n")
end

.render_pending(plan) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/better_auth/hanami/migration.rb', line 26

def render_pending(plan)
  created_tables = plan.to_create.map(&:table_name).to_set
  lines = [
    "# frozen_string_literal: true",
    "",
    "require \"date\"",
    "require \"rom-sql\"",
    "",
    "ROM::SQL.migration do",
    "  change do"
  ]
  plan.to_create.each { |change| lines.concat(create_table_lines(change.table, plan.tables)) }
  plan.to_add.each { |change| lines.concat(alter_table_lines(change, plan.tables)) }
  plan.to_index.reject { |change| created_tables.include?(change.table_name) }.each do |change|
    lines.concat(alter_table_index_lines(change))
  end
  lines.concat(["  end", "end", ""])
  lines.join("\n")
end

.sequel_dialect(connection) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/better_auth/hanami/migration.rb', line 66

def sequel_dialect(connection)
  type = connection.respond_to?(:database_type) ? connection.database_type.to_s : ""
  case type
  when /postgres/
    :postgres
  when /mysql/
    :mysql
  when /sqlite/
    :sqlite
  when /mssql|sqlserver|sql_server/
    :mssql
  else
    :postgres
  end
end