Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ColumnDefinition to contain options hash #27890

Closed
wants to merge 4 commits into from

Conversation

kamipo
Copy link
Member

@kamipo kamipo commented Feb 3, 2017

Column options are passed as an hash args then used as options hash in
add_column_options!. Converting args to attributes is inconvinient for
using options as an hash.

Column options are passed as an hash args then used as `options` hash in
`add_column_options!`. Converting args to attributes is inconvinient for
using options as an hash.
Copy link
Member

@jeremy jeremy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the improvement to creating column definitions. Much simpler.

But the tradeoff is that code is accessing column.options[…] directly and breaking compat with other adapters.

Perhaps there's a path to using options hash and keyword args to construct column definitions, but expose an opaque attr-style API for the option values.

primary_key || type.to_sym == :primary_key
ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
def [](key)
options[key]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This poses a compatibility challenge for third party database adapters. Could we offer an gentler upgrade path? Possibly by deprecating the old methods:

class ColumnDefinition
  attr_reader :name, :type, :sql_type, :options

  def initialize(*args, options: nil)
    if options
      initialize_with_options(*args, options)
    else
      initialize_with_struct_args(*args)
    end
  end

  def initialize_with_options(name, type, sql_type, options)
    @name, @type, @options, @sql_type = name, type, options, sql_type
  end

  def initialize_with_struct_args(name, type, limit, etc…)
    ActiveSupport::Deprecation.warn 
    @name, @type, @sql_type = name, type, sql_type
    @options = { limit: limit, precision: precision,  }
  end

  %i[ limit precision  ].each do |option_name|
    define_method(option_name) do
      ActiveSupport::Deprecation.warn 
      options[option_name]
    end

    define_method("#{option_name}=") do |value|
      ActiveSupport::Deprecation.warn 
      options[option_name] = value
    end
  end
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading the rest of the PR, perhaps we could provide these reader/writer methods without deprecation.

@@ -305,7 +305,7 @@ def column(name, type, options = {})
type = type.to_sym if type
options = options.dup

if @columns_hash[name] && @columns_hash[name].primary_key?
if @columns_hash[name] && @columns_hash[name][:primary_key]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage feels like a regression. It's nice to call a clear predicate method to ask about the column's attributes rather than digging in to the column options to fetch the data.

@@ -360,28 +360,16 @@ def references(*args, **options)
end
alias :belongs_to :references

def new_column_definition(name, type, options) # :nodoc:
def new_column_definition(name, type, **options) # :nodoc:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+keyword args for options.


def create_column_definition(name, type)
PostgreSQL::ColumnDefinition.new name, type
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice simplification. Pays off.

class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
private
def visit_ColumnDefinition(o)
o.sql_type = type_to_sql(o.type, o[:limit], o[:precision], o[:scale], o[:array])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this repeated. Good candidate to switch to keyword args also.

options[:null] = false if o.primary_key
options
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

self.precision = options[:precision] if options.include?(:precision)
self.scale = options[:scale] if options.include?(:scale)
self.collation = options[:collation] if options.include?(:collation)
self.options = options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to overwrite all the existing column options instead of just the ones provided by change_column.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah... good catch. I'll fix it.

@@ -418,21 +418,15 @@ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
alter_table(table_name) do |definition|
definition[column_name].null = null
definition[column_name].options[:null] = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These cases of modifying a data collection directly feel like a step back from calling specific writer methods.

It's nice to call a clear predicate method to ask about the column's
attributes rather than digging in to the column options to fetch the
data.
@kamipo
Copy link
Member Author

kamipo commented Feb 8, 2017

I addressed to switch to keyword args for type_to_sql and define reader/writer methods for options.

Originally ColumnDefiniton is a short-lived object and is only used in create_table, change_table, change_column, change_column_null, and change_column_default.

So it is enough to address only the changes of type_to_sql for third party adapters.

I created a patch for oracle-enhanced and sqlserver-adapter.

oracle-enhanced:
https://gist.github.com/kamipo/d718a823f61d13db736975fd2d7b3a5b#file-oracle-enhanced-patch

sqlserver-adapter:
https://gist.github.com/kamipo/d718a823f61d13db736975fd2d7b3a5b#file-sqlserver-adapter-patch

How about it?

@jeremy
Copy link
Member

jeremy commented Feb 9, 2017

Merged @ ae39b1a

@jeremy jeremy closed this Feb 9, 2017
@kamipo kamipo deleted the refactor_column_definition branch February 9, 2017 07:36
@yahonda
Copy link
Member

yahonda commented Feb 9, 2017

@kamipo Thanks for making a patch for Oracle enhanced adapter. Would you mind pushing the branch to github. of course pull request is welcomed.

@kamipo
Copy link
Member Author

kamipo commented Feb 9, 2017

Yeah, I'll create the branch and pull request.

kamipo added a commit to kamipo/oracle-enhanced that referenced this pull request Feb 9, 2017
column.as = options[:as]
column
options[:primary_key] ||= type == :primary_key
options[:null] = false if options[:primary_key]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kamipo @jeremy , I'm wondering why this got added here? Just noticed this got moved from the sqlite3 adapter into abstract. Not sure why a column could not be null if you specify the primary_key option.

Sorry if you get a duplicate notification, for some reason my previous comment got deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants