class Aam::Generator

Public Class Methods

new(klass, options = {}) click to toggle source
# File lib/aam/generator.rb, line 7
def initialize(klass, options = {})
  @klass = klass
  @options = {
    :skip_columns => [],
    :debug => false,
  }.merge(options)
  @memos = []
end

Public Instance Methods

generate() click to toggle source
# File lib/aam/generator.rb, line 16
def generate
  columns = @klass.columns.reject { |e|
    @options[:skip_columns].include?(e.name)
  }
  rows = columns.collect {|e|
    {
      "name"  => e.name,
      "desc"  => column_to_human_name(e.name),
      "type"  => column_type_inspect_of(e),
      "opts"  => column_attribute_inspect_of(e),
      "refs"  => reflections_inspect_of(e),
      "index" => index_info(e),
    }
  }
  out = []
  out << "#{SCHEMA_HEADER}\n#\n"
  out << "# #{@klass.model_name.human} (#{@klass.table_name} as #{@klass.name})\n"
  out << "#\n"
  out << rows.to_t.lines.collect { |e| "# #{e}" }.join
  if @memos.present?
    out << "#\n"
    out << "#- Remarks ----------------------------------------------------------------------\n"
    out << @memos.sort.collect{|row|"# #{row}\n"}.join
    out << "#--------------------------------------------------------------------------------\n"
  end
  out.join
end

Private Instance Methods

belongs_to_column?(column) click to toggle source

belongs_to のカラムか?

# File lib/aam/generator.rb, line 278
def belongs_to_column?(column)
  @klass.reflections.any? do |key, reflection|
    if reflection.macro == :belongs_to
      if reflection.respond_to?(:foreign_key)
        reflection.foreign_key.to_s == column.name
      end
    end
  end
end
belongs_to_model_has_many_syntax(column, reflection) click to toggle source

belongs_to :user している場合 User モデルから has_many :articles されていることを確認。

  1. assoc_reflection.foreign_key.to_s == column.name という比較では foreign_key 指定されると不一致になるので注意すること。

と書いたけど不一致になってもよかった。これでリレーション正しく貼られてないと判断してよい。 理由は belongs_to に foreign_key が指定されたら has_many 側も has_many :foos, :foreign_key => “bar_id” とならないといけないため。

# File lib/aam/generator.rb, line 180
def belongs_to_model_has_many_syntax(column, reflection)
  assoc_key, assoc_reflection = reflection.class_name.constantize.reflections.find do |assoc_key, assoc_reflection|
    if false
      r = reflection.class_name.constantize == assoc_reflection.active_record && [:has_many, :has_one].include?(assoc_reflection.macro)
    else
      r = assoc_reflection.respond_to?(:foreign_key) && assoc_reflection.foreign_key.to_s == column.name
    end
    if r
      syntax = ["#{assoc_reflection.macro} :#{assoc_reflection.name}"]
      if assoc_reflection.options[:foreign_key]
        syntax << "foreign_key: :#{assoc_reflection.options[:foreign_key]}"
      end
      # memo_puts "#{@klass.name} モデルは #{assoc_reflection.active_record} モデルから #{syntax.join(', ')} されています。"
      memo_puts "#{assoc_reflection.active_record}.#{syntax.join(', ')}"
      r
    end
  end
  unless assoc_reflection
    syntax = ["has_many :#{@klass.name.underscore.pluralize}"]
    if false
      # has_many :sub_articles の場合デフォルトで SubArticle を見るため不要
      syntax << ":class_name => \"#{@klass.name}\""
    end
    if reflection.options[:foreign_key]
      syntax << ":foreign_key => :#{reflection.options[:foreign_key]}"
    end
    memo_puts "【警告:リレーション欠如】#{reflection.class_name}モデルで #{syntax.join(', ')} されていません"
  end
end
column_attribute_inspect_of(column) click to toggle source
# File lib/aam/generator.rb, line 71
def column_attribute_inspect_of(column)
  attrs = []
  unless column.default.nil?
    default = column.default
    if default.kind_of? BigDecimal
      default = default.to_f
      if default.zero?
        default = 0
      end
    end
    attrs << "DEFAULT(#{default})"
  end
  unless column.null
    attrs << "NOT NULL"
  end
  if column.name == @klass.primary_key
    attrs << "PK"
  end
  attrs * " "
end
column_to_human_name(name) click to toggle source

カラム翻訳

ja.rb:
  :item => "アイテム"

実行結果:
  column_to_human_name("item")    #=> "アイテム"
  column_to_human_name("item_id") #=> "アイテムID"
# File lib/aam/generator.rb, line 219
def column_to_human_name(name)
  resp = nil
  suffixes = {
    :id => "ID",
    :type => "タイプ",
  }
  suffixes.each do |key, value|
    if md = name.match(/(?<name_without_suffix>\w+)_#{key}$/)
      # サフィックス付きのまま明示的に翻訳されている場合はそれを使う
      resp = @klass.human_attribute_name(name, :default => "").presence
      # サフィックスなしが明示的に翻訳されていたらそれを使う
      unless resp
        if v = @klass.human_attribute_name(md[:name_without_suffix], :default => "").presence
          resp = "#{v}#{value}"
        end
      end
    end
    if resp
      break
    end
  end
  # 翻訳が効いてないけどid付きのまま仕方なく変換する
  resp ||= @klass.human_attribute_name(name)
end
column_type_inspect_of(column) click to toggle source
# File lib/aam/generator.rb, line 46
def column_type_inspect_of(column)
  size = nil
  if column.type.to_s == "decimal"
    size = "(#{column.precision}, #{column.scale})"
  else
    if column.limit
      size = "(#{column.limit})"
    end
  end

  # シリアライズされているかチェック
  serialized_klass = nil
  if @klass.respond_to?(:serialized_attributes) # Rails5 から無くなったため存在チェック
    if serialized_klass = @klass.serialized_attributes[column.name]
      if serialized_klass.kind_of? ActiveRecord::Coders::YAMLColumn
        serialized_klass = "=> #{serialized_klass.object_class}"
      else
        serialized_klass = "=> #{serialized_klass}"
      end
    end
  end

  "#{column.type}#{size} #{serialized_klass}".squish
end
index_check(column) click to toggle source

指定のカラムがインデックスを貼るべきかどうかを表示する

# File lib/aam/generator.rb, line 291
def index_check(column)
  if column.name.match(/(\w+)_id\z/) || belongs_to_column?(column)
    # belongs_to :xxx, polymorphic: true の場合は xxx_id と xxx_type のペアでインデックスを貼る
    if (md = column.name.match(/(\w+)_id\z/)) && (type_column = @klass.columns_hash["#{md.captures.first}_type"])
      unless index_column?(column) && index_column?(type_column)
        memo_puts "[Warning: Need to add index] create_#{@klass.table_name} マイグレーションに add_index :#{@klass.table_name}, [:#{column.name}, :#{type_column.name}] を追加してください"
      end
    else
      unless index_column?(column)
        memo_puts "[Warning: Need to add index] create_#{@klass.table_name} マイグレーションに add_index :#{@klass.table_name}, :#{column.name} を追加してください"
      end
    end
  end
end
index_column?(column) click to toggle source

指定のカラムは何かのインデックスに含まれているか?

# File lib/aam/generator.rb, line 270
def index_column?(column)
  indexes = @klass.connection.indexes(@klass.table_name)
  indexes.any?{|e|e.columns.include?(column.name)}
end
index_info(column) click to toggle source

インデックス情報の取得

add_index :articles, :name                  #=> "I"
add_index :articles, :name, :unique => true #=> "UI"
# File lib/aam/generator.rb, line 250
def index_info(column)
  indexes = @klass.connection.indexes(@klass.table_name)
  # 関係するインデックスに絞る
  indexes2 = indexes.find_all {|e| e.columns.include?(column.name) }
  indexes2.collect {|e|
    mark = ""
    # そのインデックスは何番目にあるかを調べる
    mark << ("A".."Z").to_a.at(indexes.index(e)).to_s
    # ユニークなら「!」
    if e.unique
      mark << "!"
    end
    # mark << e.columns.size.to_s # 1なら単独、2ならペア、3ならトリプル指定みたいなのわかる
    mark
  }.join(" ")
end
memo_puts(str) click to toggle source
# File lib/aam/generator.rb, line 306
def memo_puts(str)
  if @options[:debug]
    Aam.logger.debug str if Aam.logger
  end
  @memos << str
  nil
end
reflection_inspect_of(column, reflection) click to toggle source
# File lib/aam/generator.rb, line 152
def reflection_inspect_of(column, reflection)
  return unless reflection.macro == :belongs_to
  desc = nil
  if reflection.options[:polymorphic]
    if true
      # >= 3.1.3
      target = "(#{reflection.name}_type)##{reflection.active_record.primary_key}"
    else
      # < 3.1.3
      target = "(#{reflection.options[:foreign_type]})##{reflection.active_record.primary_key}"
    end
  else
    target = "#{reflection.class_name}##{reflection.active_record.primary_key}"
    desc = belongs_to_model_has_many_syntax(column, reflection)
  end
  assoc_name = ""
  unless "#{reflection.name}_id" == column.name
    assoc_name = ":#{reflection.name}"
  end
  "#{assoc_name} => #{target} #{desc}".squish
end
reflections_inspect_ary_of(column) click to toggle source
# File lib/aam/generator.rb, line 96
def reflections_inspect_ary_of(column)
  if column.name == @klass.inheritance_column             # カラムが "type" のとき
    return "SpecificModel(STI)"
  end

  index_check(column)

  my_refrections = @klass.reflections.find_all do |key, reflection|
    if !reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) && reflection.respond_to?(:foreign_key)
      reflection.foreign_key.to_s == column.name
    end
  end

  if my_refrections.empty?
    # "xxx_id" は belongs_to されていることを確認
    if md = column.name.match(/(\w+)_id\z/)
      name = md.captures.first
      if @klass.column_names.include?("#{name}_type")
        syntax = "belongs_to :#{name}, polymorphic: true"
      else
        syntax = "belongs_to :#{name}"
      end
      memo_puts "[Warning: Need to add relation] #{@klass} モデルに #{syntax} を追加してください"
    else
      # "xxx_type" は polymorphic 指定されていることを確認
      key, reflection = @klass.reflections.find do |key, reflection|
        _options = reflection.options
        if true
          # >= 3.1.3
          _options[:polymorphic] && column.name == "#{key}_type"
        else
          # < 3.1.3
          _options[:polymorphic] && _options[:foreign_type] == column.name
        end
      end
      if reflection
        "SpecificModel(polymorphic)"
      end
    end
  else
    # 一つのカラムを複数の方法で利用している場合に対応するため回している。
    my_refrections.collect do |key, reflection|
      begin
        reflection_inspect_of(column, reflection)
      rescue NameError => error
        if @options[:debug]
          puts "--------------------------------------------------------------------------------"
          puts "【警告】以下のクラスがないため NameError になっちゃってます"
          p error
          puts "--------------------------------------------------------------------------------"
        end
      end
    end
  end
end
reflections_inspect_of(column) click to toggle source
# File lib/aam/generator.rb, line 92
def reflections_inspect_of(column)
  [reflections_inspect_ary_of(column)].flatten.compact.sort.join(" と ")
end