Module: DeclareSchema::Model::ClassMethods

Defined in:
lib/declare_schema/model.rb

Instance Method Summary collapse

Instance Method Details

#_declared_primary_keyObject

returns the primary key (String) as declared with primary_key = unlike the ‘primary_key` method, DOES NOT query the database to find the actual primary key in use right now if no explicit primary key set, returns the _default_declared_primary_key



205
206
207
208
209
# File 'lib/declare_schema/model.rb', line 205

def _declared_primary_key
  if defined?(@primary_key)
    @primary_key&.to_s
  end || _default_declared_primary_key
end

#_infer_fk_limit(fkey, refl) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/declare_schema/model.rb', line 174

def _infer_fk_limit(fkey, refl)
  if refl.options[:polymorphic]
    if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
      fkey_column.limit
    end
  else
    klass = refl.klass or raise "Couldn't find belongs_to klass for #{name} in #{refl.inspect}"
    if (pk_id_type = klass._table_options&.[](:id))
      if pk_id_type == :integer
        4
      end
    else
      if klass.table_exists? && (pk_column = klass.columns_hash[klass._declared_primary_key])
        pk_id_type = pk_column.type
        if pk_id_type == :integer
          pk_column.limit
        end
      end
    end
  end
end

#attr_type(name) ⇒ Object

Returns the type (a class) for a given field or association. If the association is a collection (has_many or habtm) return the AssociationReflection instead



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/declare_schema/model.rb', line 313

public \
def attr_type(name)
  if attr_types.nil? && self != self.name.constantize
    raise "attr_types called on a stale class object (#{self.name}). Avoid storing persistent references to classes"
  end

  attr_types[name] ||
    if (refl = reflections[name.to_s])
      if refl.macro.in?([:has_one, :belongs_to]) && !refl.options[:polymorphic]
        refl.klass
      else
        refl
      end
    end ||
    if (col = _column(name.to_s))
      DeclareSchema::PLAIN_TYPES[col.type] || col.klass
    end
end

#belongs_to(name, scope = nil, **options) ⇒ Object

Extend belongs_to so that it

  1. creates a FieldSpec for the foreign key

  2. declares an index on the foreign key

  3. declares a foreign_key constraint



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
# File 'lib/declare_schema/model.rb', line 106

def belongs_to(name, scope = nil, **options)
  column_options = {}

  column_options[:null] = if options.has_key?(:null)
                            options.delete(:null)
                          elsif options.has_key?(:optional)
                            options[:optional] # infer :null from :optional
                          end || false
  column_options[:default] = options.delete(:default) if options.has_key?(:default)
  if options.has_key?(:limit)
    options.delete(:limit)
    ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
  end

  index_options = {}
  index_options[:name]   = options.delete(:index) if options.has_key?(:index)
  index_options[:unique] = options.delete(:unique) if options.has_key?(:unique)
  index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)

  fk_options = options.dup
  fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
  fk_options[:index_name] = index_options[:name]

  fk = options[:foreign_key]&.to_s || "#{name}_id"

  if !options.has_key?(:optional)
    options[:optional] = column_options[:null] # infer :optional from :null
  end

  fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)

  if ActiveSupport::VERSION::MAJOR >= 5
    super
  else
    super(name, scope, options.except(:optional))
  end

  refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
  fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
  fkey_id_column_options = column_options.dup

  # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
  # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
  # But we can't here, because that will mess up the autoloader to follow every belongs_to association right
  # when it is declared. So instead we assume :bigint (integer limit: 8) below, while also registering this
  # pre_migration: callback to double-check that assumption Just In Time--right before we generate a migration.
  #
  # The one downside of this approach is that application code that asks the field_spec for the declared
  # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
  # a limit: 4 primary key. It seems unlikely that any application code would do this.
  fkey_id_column_options[:pre_migration] = ->(field_spec) do
    if (inferred_limit = _infer_fk_limit(fkey, refl))
      field_spec.sql_options[:limit] = inferred_limit
    end
  end

  declare_field(fkey.to_sym, :bigint, fkey_id_column_options)

  if refl.options[:polymorphic]
    foreign_type = options[:foreign_type] || "#{name}_type"
    _declare_polymorphic_type_field(foreign_type, column_options)
    index([foreign_type, fkey], index_options) if index_options[:name] != false
  else
    index(fkey, index_options) if index_options[:name] != false
    constraint(fkey, fk_options) if fk_options[:constraint_name] != false
  end
end

#constraint(fkey, options = {}) ⇒ Object



67
68
69
70
71
72
# File 'lib/declare_schema/model.rb', line 67

def constraint(fkey, options = {})
  fkey_s = fkey.to_s
  unless constraint_specs.any? { |constraint_spec| constraint_spec.foreign_key == fkey_s }
    constraint_specs << DeclareSchema::Model::ForeignKeyDefinition.new(self, fkey, options)
  end
end

#declare_field(name, type, *args, **options) ⇒ Object

Declare named field with a type and an arbitrary set of arguments. The arguments are forwarded to the #field_added callback, allowing custom metadata to be added to field declarations.



84
85
86
87
88
89
90
91
92
# File 'lib/declare_schema/model.rb', line 84

def declare_field(name, type, *args, **options)
  try(:field_added, name, type, args, options)
  _add_serialize_for_field(name, type, options)
  _add_formatting_for_field(name, type)
  _add_validations_for_field(name, type, args, options)
  _add_index_for_field(name, args, options)
  field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
  attr_order << name unless attr_order.include?(name)
end

#ignore_index(index_name) ⇒ Object

tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes that can’t be automatically generated (for example: a prefix index in MySQL)



76
77
78
# File 'lib/declare_schema/model.rb', line 76

def ignore_index(index_name)
  ignore_indexes << index_name.to_s
end

#index(fields, options = {}) ⇒ Object



55
56
57
58
59
60
61
# File 'lib/declare_schema/model.rb', line 55

def index(fields, options = {})
  # make index idempotent
  index_fields_s = Array.wrap(fields).map(&:to_s)
  unless index_definitions.any? { |index_spec| index_spec.fields == index_fields_s }
    index_definitions << ::DeclareSchema::Model::IndexDefinition.new(self, fields, options)
  end
end

#index_definitions_with_primary_keyObject



94
95
96
97
98
99
100
# File 'lib/declare_schema/model.rb', line 94

def index_definitions_with_primary_key
  if index_definitions.any?(&:primary_key?)
    index_definitions
  else
    index_definitions + [_rails_default_primary_key]
  end
end

#primary_keyObject



197
198
199
# File 'lib/declare_schema/model.rb', line 197

def primary_key
  super || 'id'
end

#primary_key_index(*fields) ⇒ Object



63
64
65
# File 'lib/declare_schema/model.rb', line 63

def primary_key_index(*fields)
  index(fields.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
end