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



192
193
194
195
196
# File 'lib/declare_schema/model.rb', line 192

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

#_infer_fk_limit(fkey, refl) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/declare_schema/model.rb', line 167

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



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/declare_schema/model.rb', line 298

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



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

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)

  super

  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



64
65
66
67
68
69
# File 'lib/declare_schema/model.rb', line 64

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.



81
82
83
84
85
86
87
88
89
# File 'lib/declare_schema/model.rb', line 81

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)



73
74
75
# File 'lib/declare_schema/model.rb', line 73

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

#index(fields, **options) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/declare_schema/model.rb', line 52

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



91
92
93
94
95
96
97
# File 'lib/declare_schema/model.rb', line 91

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

#primary_key_index(*fields) ⇒ Object



60
61
62
# File 'lib/declare_schema/model.rb', line 60

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