1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# frozen_string_literal: true
require "active_support/time"
module Rails
module Generators
class GeneratedAttribute # :nodoc:
INDEX_OPTIONS = %w(index uniq)
UNIQ_INDEX_OPTIONS = %w(uniq)
attr_accessor :name, :type
attr_reader :attr_options
attr_writer :index_name
class << self
def parse(column_definition)
name, type, has_index = column_definition.split(":")
# if user provided "name:index" instead of "name:string:index"
# type should be set blank so GeneratedAttribute's constructor
# could set it to :string
has_index, type = type, nil if INDEX_OPTIONS.include?(type)
type, attr_options = *parse_type_and_options(type)
type = type.to_sym if type
if type && reference?(type)
if UNIQ_INDEX_OPTIONS.include?(has_index)
attr_options[:index] = { unique: true }
end
end
new(name, type, has_index, attr_options)
end
def reference?(type)
[:references, :belongs_to].include? type
end
private
# parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to
# when declaring options curly brackets should be used
def parse_type_and_options(type)
case type
when /(string|text|binary|integer)\{(\d+)\}/
return $1, limit: $2.to_i
when /decimal\{(\d+)[,.-](\d+)\}/
return :decimal, precision: $1.to_i, scale: $2.to_i
when /(references|belongs_to)\{(.+)\}/
type = $1
provided_options = $2.split(/[,.-]/)
options = Hash[provided_options.map { |opt| [opt.to_sym, true] }]
return type, options
else
return type, {}
end
end
end
def initialize(name, type = nil, index_type = false, attr_options = {})
@name = name
@type = type || :string
@has_index = INDEX_OPTIONS.include?(index_type)
@has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
@attr_options = attr_options
end
def field_type
@field_type ||= case type
when :integer then :number_field
when :float, :decimal then :text_field
when :time then :time_select
when :datetime, :timestamp then :datetime_select
when :date then :date_select
when :text then :text_area
when :boolean then :check_box
else
:text_field
end
end
def default
@default ||= case type
when :integer then 1
when :float then 1.5
when :decimal then "9.99"
when :datetime, :timestamp, :time then Time.now.to_s(:db)
when :date then Date.today.to_s(:db)
when :string then name == "type" ? "" : "MyString"
when :text then "MyText"
when :boolean then false
when :references, :belongs_to then nil
else
""
end
end
def plural_name
name.sub(/_id$/, "").pluralize
end
def singular_name
name.sub(/_id$/, "").singularize
end
def human_name
name.humanize
end
def index_name
@index_name ||= if polymorphic?
%w(id type).map { |t| "#{name}_#{t}" }
else
column_name
end
end
def column_name
@column_name ||= reference? ? "#{name}_id" : name
end
def foreign_key?
!!(name =~ /_id$/)
end
def reference?
self.class.reference?(type)
end
def polymorphic?
attr_options[:polymorphic]
end
def required?
attr_options[:required]
end
def has_index?
@has_index
end
def has_uniq_index?
@has_uniq_index
end
def password_digest?
name == "password" && type == :digest
end
def token?
type == :token
end
def inject_options
"".dup.tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } }
end
def inject_index_options
has_uniq_index? ? ", unique: true" : ""
end
def options_for_migration
@attr_options.dup.tap do |options|
if required?
options.delete(:required)
options[:null] = false
end
if reference? && !polymorphic?
options[:foreign_key] = true
end
end
end
end
end
end
|