summaryrefslogtreecommitdiff
path: root/doc/development/creating_enums.md
blob: 1a72d99112f0935b30462fdc3df9593ee1f9684e (plain)
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
---
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

# Creating enums

When creating a new enum, it should use the database type `SMALLINT`.
The `SMALLINT` type size is 2 bytes, which is sufficient for an enum.
This would help to save space in the database.

To use this type, add `limit: 2` to the migration that creates the column.

Example:

```ruby
def change
  add_column :ci_job_artifacts, :file_format, :integer, limit: 2
end
```

## All of the key/value pairs should be defined in FOSS

**Summary:** All enums needs to be defined in FOSS, if a model is also part of the FOSS.

```ruby
class Model < ApplicationRecord
  enum platform: {
    aws: 0,
    gcp: 1      # EE-only
  }
end
```

When you add a new key/value pair to a `enum` and if it's EE-specific, you might be
tempted to organize the `enum` as the following:

```ruby
# Define `failure_reason` enum in `Pipeline` model:
class Pipeline < ApplicationRecord
  enum failure_reason: Enums::Pipeline.failure_reasons
end
```

```ruby
# Define key/value pairs that used in FOSS and EE:
module Enums
  module Pipeline
    def self.failure_reasons
      { unknown_failure: 0, config_error: 1 }
    end
  end
end

Enums::Pipeline.prepend_if_ee('EE::Enums::Pipeline')
```

```ruby
# Define key/value pairs that used in EE only:
module EE
  module Enums
    module Pipeline
      override :failure_reasons
      def failure_reasons
        super.merge(activity_limit_exceeded: 2)
      end
    end
  end
end
```

This works as-is, however, it has a couple of downside that:

- Someone could define a key/value pair in EE that is **conflicted** with a value defined in FOSS.
  e.g. Define `activity_limit_exceeded: 1` in `EE::Enums::Pipeline`.
- When it happens, the feature works totally different.
  e.g. We cannot figure out `failure_reason` is either `config_error` or `activity_limit_exceeded`.
- When it happens, we have to ship a database migration to fix the data integrity,
  which might be impossible if you cannot recover the original value.

Also, you might observe a workaround for this concern by setting an offset in EE's values.
For example, this example sets `1000` as the offset:

```ruby
module EE
  module Enums
    module Pipeline
      override :failure_reasons
      def failure_reasons
        super.merge(activity_limit_exceeded: 1_000, size_limit_exceeded: 1_001)
      end
    end
  end
end
```

This looks working as a workaround, however, this approach has some downsides that:

- Features could move from EE to FOSS or vice versa. Therefore, the offset might be mixed between FOSS and EE in the future.
  e.g. When you move `activity_limit_exceeded` to FOSS, you'll see `{ unknown_failure: 0, config_error: 1, activity_limit_exceeded: 1_000 }`.
- The integer column for the `enum` is likely created [as `SMALLINT`](#creating-enums).
  Therefore, you need to be careful of that the offset doesn't exceed the maximum value of 2 bytes integer.

As a conclusion, you should define all of the key/value pairs in FOSS.
For example, you can simply write the following code in the above case:

```ruby
class Pipeline < ApplicationRecord
  enum failure_reason: {
    unknown_failure: 0,
    config_error: 1,
    activity_limit_exceeded: 2
  }
end
```

## Add new values in the gap

After merging some EE and FOSS enums, there might be a gap between the two groups of values:

```ruby
module Enums
  module Ci
    module CommitStatus
      def self.failure_reasons
        {
          # ...
          data_integrity_failure: 12,
          forward_deployment_failure: 13,
          insufficient_bridge_permissions: 1_001,
          downstream_bridge_project_not_found: 1_002,
          # ...
        }
      end
    end
  end
end
```

To add new values, you should fill the gap first.
In the example above add `14` instead of `1_003`:

```ruby
{
  # ...
  data_integrity_failure: 12,
  forward_deployment_failure: 13,
  a_new_value: 14,
  insufficient_bridge_permissions: 1_001,
  downstream_bridge_project_not_found: 1_002,
  # ...
}
```