diff options
Diffstat (limited to 'app/models/ci')
23 files changed, 139 insertions, 90 deletions
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 697f06fbffd..b77e0f1d5c1 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -55,8 +55,6 @@ module Ci end def retryable? - return false unless Feature.enabled?(:ci_recreate_downstream_pipeline, project) - return false if failed? && (pipeline_loop_detected? || reached_max_descendant_pipelines_depth?) super diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 627604ec26c..d389c59f16b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -55,9 +55,9 @@ module Ci has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', foreign_key: :job_id, inverse_of: :job end - has_one :runner_machine_build, class_name: 'Ci::RunnerMachineBuild', foreign_key: :build_id, inverse_of: :build, + has_one :runner_manager_build, class_name: 'Ci::RunnerManagerBuild', foreign_key: :build_id, inverse_of: :build, autosave: true - has_one :runner_machine, through: :runner_machine_build, class_name: 'Ci::RunnerMachine' + has_one :runner_manager, foreign_key: :runner_machine_id, through: :runner_manager_build, class_name: 'Ci::RunnerManager' has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, foreign_key: :build_id, inverse_of: :build has_one :trace_metadata, class_name: 'Ci::BuildTraceMetadata', foreign_key: :build_id, inverse_of: :build @@ -597,8 +597,14 @@ module Ci .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self)) .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false, masked: true) .append(key: 'CI_JOB_STARTED_AT', value: started_at&.iso8601) - .append(key: 'CI_BUILD_ID', value: id.to_s) - .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false, masked: true) + + if Feature.disabled?(:ci_remove_legacy_predefined_variables, project) + variables + .append(key: 'CI_BUILD_ID', value: id.to_s) + .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false, masked: true) + end + + variables .append(key: 'CI_REGISTRY_USER', value: ::Gitlab::Auth::CI_JOB_USER) .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false, masked: true) .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 4b2be446fe3..b98fdba44ec 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -11,9 +11,11 @@ module Ci include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize include IgnorableColumns + include SafelyChangeColumnDefault self.table_name = 'p_ci_builds_metadata' self.primary_key = 'id' + columns_changing_default :partition_id partitionable scope: :build diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb index f70e1ed69ea..b9a74102641 100644 --- a/app/models/ci/build_trace.rb +++ b/app/models/ci/build_trace.rb @@ -12,7 +12,11 @@ module Ci if stream.valid? stream.limit - @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state) + @trace = Gitlab::Ci::Ansi2json.convert( + stream.stream, + state, + verify_state: Feature.enabled?(:sign_and_verify_ansi2json_state, build.project) + ) end end diff --git a/app/models/ci/build_trace_metadata.rb b/app/models/ci/build_trace_metadata.rb index 00cf1531483..4c76089617f 100644 --- a/app/models/ci/build_trace_metadata.rb +++ b/app/models/ci/build_trace_metadata.rb @@ -42,9 +42,7 @@ module Ci end def track_archival!(trace_artifact_id, checksum) - update!(trace_artifact_id: trace_artifact_id, - checksum: checksum, - archived_at: Time.current) + update!(trace_artifact_id: trace_artifact_id, checksum: checksum, archived_at: Time.current) end def archival_attempts_message diff --git a/app/models/ci/catalog/listing.rb b/app/models/ci/catalog/listing.rb index 92464cb645f..b9e777f27a0 100644 --- a/app/models/ci/catalog/listing.rb +++ b/app/models/ci/catalog/listing.rb @@ -27,7 +27,7 @@ module Ci def projects_in_namespace_visible_to_user Project .in_namespace(namespace.self_and_descendant_ids) - .public_or_visible_to_user(current_user) + .public_or_visible_to_user(current_user, ::Gitlab::Access::DEVELOPER) end end end diff --git a/app/models/ci/catalog/resource.rb b/app/models/ci/catalog/resource.rb index 1b3dec5f54d..bb4584aacae 100644 --- a/app/models/ci/catalog/resource.rb +++ b/app/models/ci/catalog/resource.rb @@ -11,6 +11,18 @@ module Ci self.table_name = 'catalog_resources' belongs_to :project + + scope :for_projects, ->(project_ids) { where(project_id: project_ids) } + + delegate :avatar_path, :description, :name, to: :project + + def versions + project.releases.order_released_desc + end + + def latest_version + versions.first + end end end end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 5a7860174ff..10f0dd865ff 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -14,6 +14,9 @@ module Ci include EachBatch include Gitlab::Utils::StrongMemoize + # NOTE: Temporarily ignore. This will will be used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106740 + ignore_column :file_final_path, remove_with: '16.1', remove_after: '2023-05-23' + enum accessibility: { public: 0, private: 1 }, _suffix: true NON_ERASABLE_FILE_TYPES = %w[trace].freeze diff --git a/app/models/ci/namespace_mirror.rb b/app/models/ci/namespace_mirror.rb index 5ea51fbe0a7..ff7e681217a 100644 --- a/app/models/ci/namespace_mirror.rb +++ b/app/models/ci/namespace_mirror.rb @@ -41,8 +41,7 @@ module Ci namespace = event.namespace traversal_ids = namespace.self_and_ancestor_ids(hierarchy_order: :desc) - upsert({ namespace_id: event.namespace_id, traversal_ids: traversal_ids }, - unique_by: :namespace_id) + upsert({ namespace_id: event.namespace_id, traversal_ids: traversal_ids }, unique_by: :namespace_id) end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2b0c79aab87..d06051c7a15 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -52,26 +52,39 @@ module Ci belongs_to :ci_ref, class_name: 'Ci::Ref', foreign_key: :ci_ref_id, inverse_of: :pipelines has_internal_id :iid, scope: :project, presence: false, - track_if: -> { !importing? }, - ensure_if: -> { !importing? }, - init: ->(pipeline, scope) do - if pipeline - pipeline.project&.all_pipelines&.maximum(:iid) || pipeline.project&.all_pipelines&.count - elsif scope - ::Ci::Pipeline.where(**scope).maximum(:iid) - end - end + track_if: -> { !importing? }, + ensure_if: -> { !importing? }, + init: ->(pipeline, scope) do + if pipeline + pipeline.project&.all_pipelines&.maximum(:iid) || pipeline.project&.all_pipelines&.count + elsif scope + ::Ci::Pipeline.where(**scope).maximum(:iid) + end + end has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline + + # + # In https://gitlab.com/groups/gitlab-org/-/epics/9991, we aim to convert all CommitStatus related models to + # Ci:Job models. With that epic, we aim to replace `statuses` with `jobs`. + # + # DEPRECATED: has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline + has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline has_many :latest_statuses_ordered_by_stage, -> { latest.order(:stage_idx, :stage) }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :latest_statuses, -> { latest }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :statuses_order_id_desc, -> { order_id_desc }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline - has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline has_many :bridges, class_name: 'Ci::Bridge', foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline has_many :generic_commit_statuses, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'GenericCommitStatus' + # + # NEW: + has_many :all_jobs, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline + has_many :current_jobs, -> { latest }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline + has_many :all_processable_jobs, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline + has_many :current_processable_jobs, -> { latest }, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline + has_many :job_artifacts, through: :builds has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id, inverse_of: :pipeline # rubocop:disable Cop/ActiveRecordDependent @@ -386,6 +399,7 @@ module Ci scope :created_before_id, -> (id) { where(arel_table[:id].lt(id)) } scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) } scope :with_pipeline_source, -> (source) { where(source: source) } + scope :preload_pipeline_metadata, -> { preload(:pipeline_metadata) } scope :outside_pipeline_family, ->(pipeline) do where.not(id: pipeline.same_family_pipeline_ids) @@ -407,11 +421,15 @@ module Ci # In general, please use `Ci::PipelinesForMergeRequestFinder` instead, # for checking permission of the actor. scope :triggered_by_merge_request, -> (merge_request) do - where(source: :merge_request_event, - merge_request: merge_request, - project: [merge_request.source_project, merge_request.target_project]) + where( + source: :merge_request_event, + merge_request: merge_request, + project: [merge_request.source_project, merge_request.target_project] + ) end + scope :order_id_desc, -> { order(id: :desc) } + # Returns the pipelines in descending order (= newest first), optionally # limited to a number of references. # @@ -682,7 +700,7 @@ module Ci # rubocop: enable CodeReuse/ServiceClass def lazy_ref_commit - BatchLoader.for(ref).batch do |refs, loader| + BatchLoader.for(ref).batch(key: project.id) do |refs, loader| next unless project.repository_exists? project.repository.list_commits_by_ref_name(refs).then do |commits| @@ -843,8 +861,7 @@ module Ci when 'manual' then block when 'scheduled' then delay else - raise Ci::HasStatus::UnknownStatusError, - "Unknown status `#{new_status}`" + raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`" end end end @@ -1319,7 +1336,7 @@ module Ci def cluster_agent_authorizations strong_memoize(:cluster_agent_authorizations) do - ::Clusters::AgentAuthorizationsFinder.new(project).execute + ::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute end end diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 83e6fa2f862..49d27053745 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -83,6 +83,8 @@ module Ci Settings.cron_jobs['pipeline_schedule_worker']['cron'] end + # Using destroy instead of before_destroy as we want nullify_dependent_associations_in_batches + # to run first and not in a transaction block. This prevents timeouts for schedules with numerous pipelines def destroy nullify_dependent_associations_in_batches diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb index 37c82c125aa..4c421f066f9 100644 --- a/app/models/ci/processable.rb +++ b/app/models/ci/processable.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module Ci + # This class is a collection of common features between Ci::Build and Ci::Bridge. + # In https://gitlab.com/groups/gitlab-org/-/epics/9991, we aim to clarify class naming conventions. class Processable < ::CommitStatus include Gitlab::Utils::StrongMemoize include FromUnion diff --git a/app/models/ci/project_mirror.rb b/app/models/ci/project_mirror.rb index 15a161d5b7c..23cd5d92730 100644 --- a/app/models/ci/project_mirror.rb +++ b/app/models/ci/project_mirror.rb @@ -13,8 +13,7 @@ module Ci class << self def sync!(event) - upsert({ project_id: event.project_id, namespace_id: event.project.namespace_id }, - unique_by: :project_id) + upsert({ project_id: event.project_id, namespace_id: event.project.namespace_id }, unique_by: :project_id) end end end diff --git a/app/models/ci/ref.rb b/app/models/ci/ref.rb index af5fdabff6e..199e1cd07e7 100644 --- a/app/models/ci/ref.rb +++ b/app/models/ci/ref.rb @@ -43,8 +43,7 @@ module Ci class << self def ensure_for(pipeline) - safe_find_or_create_by(project_id: pipeline.project_id, - ref_path: pipeline.source_ref_path) + safe_find_or_create_by(project_id: pipeline.project_id, ref_path: pipeline.source_ref_path) end def failing_state?(status_name) diff --git a/app/models/ci/resource_group.rb b/app/models/ci/resource_group.rb index a220aa7bb18..48f321a236d 100644 --- a/app/models/ci/resource_group.rb +++ b/app/models/ci/resource_group.rb @@ -58,6 +58,10 @@ module Ci end end + def current_processable + Ci::Processable.find_by('(id, partition_id) IN (?)', resources.select('build_id, partition_id')) + end + private # In order to avoid deadlock, we do NOT specify the job execution order in the same pipeline. diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 6fefe95769b..80a3d8df632 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -18,9 +18,9 @@ module Ci extend ::Gitlab::Utils::Override add_authentication_token_field :token, - encrypted: :optional, - expires_at: :compute_token_expiration, - format_with_prefix: :prefix_for_new_and_legacy_runner + encrypted: :optional, + expires_at: :compute_token_expiration, + format_with_prefix: :prefix_for_new_and_legacy_runner enum access_level: { not_protected: 0, @@ -70,7 +70,7 @@ module Ci TAG_LIST_MAX_LENGTH = 50 - has_many :runner_machines, inverse_of: :runner + has_many :runner_managers, inverse_of: :runner has_many :builds has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :runner_projects, disable_joins: true @@ -134,7 +134,7 @@ module Ci belonging_to_group(group_self_and_ancestors_ids) } - scope :belonging_to_parent_group_of_project, -> (project_id) { + scope :belonging_to_parent_groups_of_project, -> (project_id) { raise ArgumentError, "only 1 project_id allowed for performance reasons" unless project_id.is_a?(Integer) project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) @@ -148,7 +148,7 @@ module Ci from_union( [ belonging_to_project(project_id), - project.group_runners_enabled? ? belonging_to_parent_group_of_project(project_id) : nil, + project.group_runners_enabled? ? belonging_to_parent_groups_of_project(project_id) : nil, project.shared_runners ].compact, remove_duplicates: false @@ -215,16 +215,14 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at, :executor_type chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout, - error_message: 'Maximum job timeout has a value which could not be accepted' + error_message: 'Maximum job timeout has a value which could not be accepted' validates :maximum_timeout, allow_nil: true, - numericality: { greater_than_or_equal_to: 600, - message: 'needs to be at least 10 minutes' } + numericality: { greater_than_or_equal_to: 600, message: 'needs to be at least 10 minutes' } validates :public_projects_minutes_cost_factor, :private_projects_minutes_cost_factor, allow_nil: false, - numericality: { greater_than_or_equal_to: 0.0, - message: 'needs to be non-negative' } + numericality: { greater_than_or_equal_to: 0.0, message: 'needs to be non-negative' } validates :config, json_schema: { filename: 'ci_runner_config' } @@ -498,14 +496,14 @@ module Ci end end - def ensure_machine(system_xid, &blk) - RunnerMachine.safe_find_or_create_by!(runner_id: id, system_xid: system_xid.to_s, &blk) # rubocop: disable Performance/ActiveRecordSubtransactionMethods + def ensure_manager(system_xid, &blk) + RunnerManager.safe_find_or_create_by!(runner_id: id, system_xid: system_xid.to_s, &blk) # rubocop: disable Performance/ActiveRecordSubtransactionMethods end def registration_available? authenticated_user_registration_type? && created_at > REGISTRATION_AVAILABILITY_TIME.ago && - !runner_machines.any? + !runner_managers.any? end private @@ -595,7 +593,7 @@ module Ci end def exactly_one_group - unless runner_namespaces.one? + unless runner_namespaces.size == 1 errors.add(:runner, 'needs to be assigned to exactly one group') end end diff --git a/app/models/ci/runner_machine_build.rb b/app/models/ci/runner_machine_build.rb deleted file mode 100644 index d4f2c403337..00000000000 --- a/app/models/ci/runner_machine_build.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Ci - class RunnerMachineBuild < Ci::ApplicationRecord - include Ci::Partitionable - - self.table_name = :p_ci_runner_machine_builds - self.primary_key = :build_id - - partitionable scope: :build, partitioned: true - - belongs_to :build, inverse_of: :runner_machine_build, class_name: 'Ci::Build' - belongs_to :runner_machine, inverse_of: :runner_machine_builds, class_name: 'Ci::RunnerMachine' - - validates :build, presence: true - validates :runner_machine, presence: true - - scope :for_build, ->(build_id) { where(build_id: build_id) } - - def self.pluck_build_id_and_runner_machine_id - select(:build_id, :runner_machine_id) - .pluck(:build_id, :runner_machine_id) - .to_h - end - end -end diff --git a/app/models/ci/runner_machine.rb b/app/models/ci/runner_manager.rb index 8cf395aadb4..e36024d9f5b 100644 --- a/app/models/ci/runner_machine.rb +++ b/app/models/ci/runner_manager.rb @@ -1,20 +1,23 @@ # frozen_string_literal: true module Ci - class RunnerMachine < Ci::ApplicationRecord + class RunnerManager < Ci::ApplicationRecord include FromUnion include RedisCacheable include Ci::HasRunnerExecutor + # For legacy reasons, the table name is ci_runner_machines in the database + self.table_name = 'ci_runner_machines' + # The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner Machine DB entry can be updated UPDATE_CONTACT_COLUMN_EVERY = (40.minutes)..(55.minutes) belongs_to :runner - has_many :runner_machine_builds, inverse_of: :runner_machine, class_name: 'Ci::RunnerMachineBuild' - has_many :builds, through: :runner_machine_builds, class_name: 'Ci::Build' - belongs_to :runner_version, inverse_of: :runner_machines, primary_key: :version, foreign_key: :version, - class_name: 'Ci::RunnerVersion' + has_many :runner_manager_builds, inverse_of: :runner_manager, class_name: 'Ci::RunnerManagerBuild' + has_many :builds, through: :runner_manager_builds, class_name: 'Ci::Build' + belongs_to :runner_version, inverse_of: :runner_managers, primary_key: :version, foreign_key: :version, + class_name: 'Ci::RunnerVersion' validates :runner, presence: true validates :system_xid, presence: true, length: { maximum: 64 } @@ -27,7 +30,7 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at, :executor_type - # The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner machine + # The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner manager # will be considered stale STALE_TIMEOUT = 7.days diff --git a/app/models/ci/runner_manager_build.rb b/app/models/ci/runner_manager_build.rb new file mode 100644 index 00000000000..322c5ae3a68 --- /dev/null +++ b/app/models/ci/runner_manager_build.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Ci + class RunnerManagerBuild < Ci::ApplicationRecord + include Ci::Partitionable + + self.table_name = :p_ci_runner_machine_builds + self.primary_key = :build_id + + partitionable scope: :build, partitioned: true + + alias_attribute :runner_manager_id, :runner_machine_id + + belongs_to :build, inverse_of: :runner_manager_build, class_name: 'Ci::Build' + belongs_to :runner_manager, foreign_key: :runner_machine_id, inverse_of: :runner_manager_builds, + class_name: 'Ci::RunnerManager' + + validates :build, presence: true + validates :runner_manager, presence: true + + scope :for_build, ->(build_id) { where(build_id: build_id) } + + def self.pluck_build_id_and_runner_manager_id + select(:build_id, :runner_manager_id) + .pluck(:build_id, :runner_manager_id) + .to_h + end + end +end diff --git a/app/models/ci/runner_version.rb b/app/models/ci/runner_version.rb index 41e7a2b8e8a..03b50f13989 100644 --- a/app/models/ci/runner_version.rb +++ b/app/models/ci/runner_version.rb @@ -19,7 +19,7 @@ module Ci recommended: 'Upgrade is available and recommended for the runner.' }.freeze - has_many :runner_machines, inverse_of: :runner_version, foreign_key: :version, class_name: 'Ci::RunnerMachine' + has_many :runner_managers, inverse_of: :runner_version, foreign_key: :version, class_name: 'Ci::RunnerManager' # This scope returns all versions that might need recalculating. For instance, once a version is considered # :recommended, it normally doesn't change status even if the instance is upgraded diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb index 43214b0c336..e6f80658f5d 100644 --- a/app/models/ci/running_build.rb +++ b/app/models/ci/running_build.rb @@ -24,10 +24,12 @@ module Ci raise ArgumentError, 'build has not been picked by a shared runner' end - entry = self.new(build: build, - project: build.project, - runner: build.runner, - runner_type: build.runner.runner_type) + entry = self.new( + build: build, + project: build.project, + runner: build.runner, + runner_type: build.runner.runner_type + ) entry.validate! diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 02093bdf153..d61760bd0fc 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -112,8 +112,7 @@ module Ci when 'scheduled' then delay when 'skipped', nil then skip else - raise Ci::HasStatus::UnknownStatusError, - "Unknown status `#{new_status}`" + raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`" end end end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 1b2a7dc3fe4..0cfe2d50283 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -26,8 +26,7 @@ module Ci mode: :per_attribute_iv, algorithm: 'aes-256-gcm', key: Settings.attr_encrypted_db_key_base_32, - encode: false, - encode_vi: false + encode: false before_validation :set_default_values |