From 0c0b16508be2fe762c62073b38a640c921b0ee49 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 2 Jun 2017 09:10:51 +0200 Subject: [PATCH] Fix/bump representable (#5465) * bump reform and roar -> bumps representer * adapt to changed validation interface * disable initializer patch for now * adapt to changed representable attr interface * can no longer have private methods inside a representer * private no longer possible for representer * bump reform * wip - restyle validation * remove commented out patch * apply injection as prescribed * reactivate reform error symbols patch * remove patch to Hash superfluous wit ruby 2.3 * remove outdated human_attribute_name patch * whitespace fixes * adapt filter name after removal of human_attribute_name patch * adapt filter specs to no longer rely on human_attribute_name patch * fix version filter name * remove reliance on no longer existing human_attribute_name patch * use correct key in journal formatter * remove private from representer * adapt to altered setter interface * reenable i18n for error messages in contracts * no private methods in representer * defined model for contracts * fix validaton * instantiate correct Object * define model for contract * circumvent now existing render method on reform * replace deprecated constant * patch correct reform class - not the module - via prepend * refactor too complex method * replace deprations * remove remnants of parentId * prevent error symbols from existing twice * adapt user representer to altered setter interface * adapt watcher representer to altered setter interface * remove now unnessary patch * adapt setter to altered interface * adapt spec * fix custom field setters * remove parentId from wp representer As the parent is a wp resource, clients should use the parent link instead * adapt spec to changed valid? interface * remove parentId from wp schema * replace references of parentId in frontend * remove TODO [ci skip] --- Gemfile | 6 +- Gemfile.lock | 41 +++-- app/assets/javascripts/admin_users.js | 2 +- app/contracts/model_contract.rb | 22 ++- app/contracts/queries/base_contract.rb | 15 +- app/contracts/relations/base_contract.rb | 50 ++---- app/contracts/relations/update_contract.rb | 7 +- app/contracts/users/base_contract.rb | 11 +- app/contracts/users/create_contract.rb | 11 +- app/contracts/users/update_contract.rb | 7 +- app/contracts/work_packages/base_contract.rb | 4 + .../work_packages/create_contract.rb | 7 +- .../work_packages/create_note_contract.rb | 4 + app/models/project.rb | 2 +- .../filter/assigned_to_filter.rb | 2 +- .../work_packages/filter/version_filter.rb | 3 +- app/models/type/attributes.rb | 68 +++++--- .../relations/create_relation_service.rb | 1 + app/views/users/_form.html.erb | 6 +- app/views/users/_simple_form.html.erb | 2 +- config/initializers/10-patches.rb | 49 +----- config/initializers/mime_types.rb | 2 +- .../hash.rb => config/initializers/reform.rb | 32 ++-- config/locales/en.yml | 6 +- docs/api/apiv3/endpoints/work-packages.apib | 44 ++--- .../work-package-resource.service.ts | 18 ++- .../modes/hierarchy/hierarchy-render-pass.ts | 30 ++-- .../wp-relations-hierarchy.directive.ts | 24 +-- .../wp-relations-hierarchy.service.ts | 38 +++-- lib/api/decorators/aggregation_group.rb | 2 - lib/api/decorators/collection.rb | 13 +- lib/api/decorators/link_object.rb | 55 ++++--- lib/api/decorators/schema_representer.rb | 2 - lib/api/decorators/single.rb | 8 +- .../attachment_metadata_representer.rb | 5 +- .../configuration_representer.rb | 7 +- .../columns/query_column_representer.rb | 3 +- .../query_filter_instance_representer.rb | 8 +- .../filters/query_filter_representer.rb | 3 +- .../group_bys/query_group_by_representer.rb | 2 - .../operators/query_operator_representer.rb | 2 - lib/api/v3/queries/query_representer.rb | 4 +- ...uery_filter_instance_schema_representer.rb | 2 - .../schemas/query_schema_representer.rb | 3 +- .../sort_bys/query_sort_by_representer.rb | 2 - lib/api/v3/relations/relations_api.rb | 2 +- .../schemas/schema_dependency_representer.rb | 3 +- .../string_object_representer.rb | 3 +- lib/api/v3/users/user_representer.rb | 67 ++++---- lib/api/v3/utilities/custom_field_injector.rb | 15 +- lib/api/v3/watchers/watcher_representer.rb | 6 +- .../v3/work_packages/create_work_packages.rb | 1 - .../schema/work_package_schema_representer.rb | 34 ++-- ...ork_package_attribute_links_representer.rb | 15 +- .../work_package_collection_representer.rb | 5 +- .../work_package_payload_representer.rb | 151 ++++++++++-------- .../work_package_relations_api.rb | 2 +- .../work_packages/work_package_representer.rb | 50 +++--- .../work_packages_shared_helpers.rb | 2 +- lib/extended_http.rb | 2 +- lib/open_project/patches/reform.rb | 54 +++++++ .../macros/timelines_wiki_macro.rb | 2 +- .../journal_formatter/named_association.rb | 4 + .../relations/create_contract_spec.rb | 48 ++++-- .../work_packages/base_contract_spec.rb | 6 +- .../work_packages/select_query_spec.rb | 2 +- spec/lib/api/contracts/model_contract_spec.rb | 9 +- .../work_package_schema_representer_spec.rb | 10 -- spec/lib/representable_spec.rb | 7 +- .../filter/version_filter_spec.rb | 2 +- .../api/v3/relations/relations_api_spec.rb | 2 +- .../api/v3/work_package_resource_spec.rb | 72 ++------- .../queries/filters/shared_filter_examples.rb | 2 +- spec/views/users/edit.html.erb_spec.rb | 6 +- 74 files changed, 658 insertions(+), 561 deletions(-) rename lib/open_project/patches/hash.rb => config/initializers/reform.rb (71%) create mode 100644 lib/open_project/patches/reform.rb diff --git a/Gemfile b/Gemfile index cc27beb4ec5..64190f2d37c 100644 --- a/Gemfile +++ b/Gemfile @@ -230,15 +230,15 @@ group :development, :test do gem 'pry-rescue', '~> 1.4.5' gem 'pry-byebug', '~> 3.4.2', platforms: [:mri] gem 'pry-doc', '~> 0.10' - end # API gems gem 'grape', '~> 0.19.2' gem 'grape-cache_control', '~> 1.0.1' -gem 'roar', '~> 1.0.0' -gem 'reform', '~> 1.2.6', require: false +gem 'reform', '~> 2.2.0' +gem 'reform-rails', '~> 0.1.7' +gem 'roar', '~> 1.1.0' platforms :mri, :mingw, :x64_mingw do group :mysql2 do diff --git a/Gemfile.lock b/Gemfile.lock index b8d65e7cc1a..4cea556557a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -252,6 +252,10 @@ GEM activemodel activesupport debug_inspector (0.0.2) + declarative (0.0.9) + declarative-builder (0.1.0) + declarative-option (< 0.2.0) + declarative-option (0.1.0) delayed_job (4.1.2) activesupport (>= 3.0, < 5.1) delayed_job_active_record (4.1.1) @@ -260,9 +264,12 @@ GEM descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.3) - disposable (0.0.9) - representable (~> 2.0) - uber + disposable (0.4.2) + declarative (>= 0.0.9, < 1.0.0) + declarative-builder (< 0.2.0) + declarative-option (< 0.2.0) + representable (>= 2.4.0, <= 3.1.0) + uber (< 0.2.0) docile (1.1.5) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) @@ -466,15 +473,16 @@ GEM ffi (>= 0.5.0) rdoc (5.1.0) redcarpet (3.3.4) - reform (1.2.6) - activemodel - disposable (~> 0.0.5) - representable (~> 2.1.0) - uber (~> 0.0.11) - representable (2.1.8) - multi_json - nokogiri - uber (~> 0.0.7) + reform (2.2.4) + disposable (>= 0.4.1) + representable (>= 2.4.0, < 3.1.0) + reform-rails (0.1.7) + activemodel (>= 3.2) + reform (>= 2.2.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) request_store (1.3.2) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) @@ -484,8 +492,8 @@ GEM mime-types (>= 1.16, < 3.0) netrc (~> 0.7) retriable (2.1.0) - roar (1.0.4) - representable (>= 2.0.1, < 2.4.0) + roar (1.1.0) + representable (~> 3.0.0) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) @@ -692,11 +700,12 @@ DEPENDENCIES rails_12factor rails_autolink (~> 1.1.6) rdoc (>= 2.4.2) - reform (~> 1.2.6) + reform (~> 2.2.0) + reform-rails (~> 0.1.7) request_store (~> 1.3.1) responders (~> 2.4) retriable (~> 2.1) - roar (~> 1.0.0) + roar (~> 1.1.0) rspec (~> 3.5.0) rspec-activemodel-mocks (~> 1.0.3)! rspec-example_disabler! diff --git a/app/assets/javascripts/admin_users.js b/app/assets/javascripts/admin_users.js index cee11559059..50ba911c3ad 100644 --- a/app/assets/javascripts/admin_users.js +++ b/app/assets/javascripts/admin_users.js @@ -52,7 +52,7 @@ if (this.value === '') { passwordFields.show(); - passwordInputs.removeProp('disabled'); + passwordInputs.prop('disabled', false); } else { passwordFields.hide(); passwordInputs.prop('disabled', 'disabled'); diff --git a/app/contracts/model_contract.rb b/app/contracts/model_contract.rb index 2efed8eddaf..be264a6f5ec 100644 --- a/app/contracts/model_contract.rb +++ b/app/contracts/model_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -45,6 +46,9 @@ class ModelContract < Reform::Contract end writable_attributes.concat attributes.map(&:to_s) + # allow the _id variant as well + writable_attributes.concat(attributes.map { |a| "#{a}_id" }) + if block attribute_validations << block end @@ -63,10 +67,10 @@ class ModelContract < Reform::Contract collect_ancestor_attributes(:writable_attributes) end - validate :readonly_attributes_unchanged - validate :run_attribute_validations - def validate + readonly_attributes_unchanged + run_attribute_validations + super model.valid? @@ -79,6 +83,18 @@ class ModelContract < Reform::Contract errors.empty? end + # Methods required to get ActiveModel error messages working + extend ActiveModel::Naming + + def self.model_name + ActiveModel::Name.new(model, nil) + end + + def self.model + raise NotImplementedError + end + # end Methods required to get ActiveModel error messages working + private def readonly_attributes_unchanged diff --git a/app/contracts/queries/base_contract.rb b/app/contracts/queries/base_contract.rb index 589b9de0f85..b74c04b46a5 100644 --- a/app/contracts/queries/base_contract.rb +++ b/app/contracts/queries/base_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -45,10 +46,11 @@ module Queries attribute :sort_criteria # => sortBy attribute :group_by # => groupBy - attr_reader :user + def self.model + Query + end - validate :validate_project - validate :user_allowed_to_make_public + attr_reader :user def initialize(query, user) super query @@ -56,6 +58,13 @@ module Queries @user = user end + def validate + validate_project + user_allowed_to_make_public + + super + end + def validate_project errors.add :project, :error_not_found if project_id.present? && !project_visible? end diff --git a/app/contracts/relations/base_contract.rb b/app/contracts/relations/base_contract.rb index f695150fa14..58883cf3b9d 100644 --- a/app/contracts/relations/base_contract.rb +++ b/app/contracts/relations/base_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -36,14 +37,25 @@ module Relations attribute :delay attribute :description - attribute :from_id - attribute :to_id + attribute :from + attribute :to - validate :user_allowed_to_access - validate :user_allowed_to_manage_relations + validate :from do + errors.add :from, :error_not_found unless visible_work_packages.exists? model.from_id + end + + validate :to do + errors.add :to, :error_not_found unless visible_work_packages.exists? model.to_id + end + + validate :manage_relations_permission? attr_reader :user + def self.model + Relation + end + def initialize(relation, user) super relation @@ -52,35 +64,7 @@ module Relations private - def fields - override_delay! super - end - - ## - # We have to redefine `#delay` in this `Reform::Contract::Fields` instance - # because it's conflicting with delayed_job's `#delay`. Without this a call - # to `fields.delay.nil?` will actually enqueue the call to `#nil?` as a delayed job - # as opposed to just checking the field for nil. - # - # This is the best I could come up with. Feel free to solve this better if you know how! - def override_delay!(fields) - @delay_overriden ||= begin - def fields.delay - @table[:delay] - end - end - - fields - end - - ## - # Allow the user only to create/update relations between work packages they are allowed to see. - def user_allowed_to_access - errors.add :from, :error_not_found unless visible_work_packages.exists? model.from_id - errors.add :to, :error_not_found unless visible_work_packages.exists? model.to_id - end - - def user_allowed_to_manage_relations + def manage_relations_permission? if !manage_relations? errors.add :base, :error_unauthorized end diff --git a/app/contracts/relations/update_contract.rb b/app/contracts/relations/update_contract.rb index 3d3b0c78dcc..1ce941fa6e0 100644 --- a/app/contracts/relations/update_contract.rb +++ b/app/contracts/relations/update_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -31,7 +32,11 @@ require 'relations/base_contract' module Relations class UpdateContract < BaseContract - validate :links_immutable + def validate + links_immutable + + super + end private diff --git a/app/contracts/users/base_contract.rb b/app/contracts/users/base_contract.rb index 0259f630abb..35712f9d87d 100644 --- a/app/contracts/users/base_contract.rb +++ b/app/contracts/users/base_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -43,7 +44,9 @@ module Users attribute :identity_url attribute :password - validate :existing_auth_source + def self.model + User + end def initialize(user, current_user) super(user) @@ -51,6 +54,12 @@ module Users @current_user = current_user end + def validate + existing_auth_source + + super + end + private attr_reader :current_user diff --git a/app/contracts/users/create_contract.rb b/app/contracts/users/create_contract.rb index ffbc454c545..a582b1e182a 100644 --- a/app/contracts/users/create_contract.rb +++ b/app/contracts/users/create_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -31,9 +32,6 @@ require 'users/base_contract' module Users class CreateContract < BaseContract - validate :user_allowed_to_add - validate :authentication_defined - attribute :status do unless model.active? || model.invited? # New users may only have these two statuses @@ -41,6 +39,13 @@ module Users end end + def validate + user_allowed_to_add + authentication_defined + + super + end + private def authentication_defined diff --git a/app/contracts/users/update_contract.rb b/app/contracts/users/update_contract.rb index ff7fce0e573..389d994204c 100644 --- a/app/contracts/users/update_contract.rb +++ b/app/contracts/users/update_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -31,7 +32,11 @@ require 'users/base_contract' module Users class UpdateContract < BaseContract - validate :user_allowed_to_update + def validate + user_allowed_to_update + + super + end private diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 35cbd463f5f..040156b7769 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -32,6 +32,10 @@ require 'model_contract' module WorkPackages class BaseContract < ::ModelContract + def self.model + WorkPackage + end + attribute :subject attribute :description attribute :start_date, :due_date diff --git a/app/contracts/work_packages/create_contract.rb b/app/contracts/work_packages/create_contract.rb index 9c7775fb5e2..3f607ce0d4f 100644 --- a/app/contracts/work_packages/create_contract.rb +++ b/app/contracts/work_packages/create_contract.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -35,7 +36,11 @@ module WorkPackages errors.add :author_id, :invalid if model.author != user end - validate :user_allowed_to_add + def validate + user_allowed_to_add + + super + end private diff --git a/app/contracts/work_packages/create_note_contract.rb b/app/contracts/work_packages/create_note_contract.rb index 59915a74853..659e66fe818 100644 --- a/app/contracts/work_packages/create_note_contract.rb +++ b/app/contracts/work_packages/create_note_contract.rb @@ -29,6 +29,10 @@ module WorkPackages class CreateNoteContract < ::ModelContract + def self.model + WorkPackage + end + attr_accessor :policy, :user diff --git a/app/models/project.rb b/app/models/project.rb index 84211cfdb72..27353099495 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -488,7 +488,7 @@ class Project < ActiveRecord::Base def types_used_by_work_packages ::Type.where(id: WorkPackage.where(project_id: project.id) .select(:type_id) - .uniq) + .distinct) end # Returns an array of the types used by the project and its active sub projects diff --git a/app/models/queries/work_packages/filter/assigned_to_filter.rb b/app/models/queries/work_packages/filter/assigned_to_filter.rb index 2dfe2386549..8eb898cf6d3 100644 --- a/app/models/queries/work_packages/filter/assigned_to_filter.rb +++ b/app/models/queries/work_packages/filter/assigned_to_filter.rb @@ -51,7 +51,7 @@ class Queries::WorkPackages::Filter::AssignedToFilter < end def human_name - WorkPackage.human_attribute_name('assigned_to_id') + WorkPackage.human_attribute_name('assigned_to') end def self.key diff --git a/app/models/queries/work_packages/filter/version_filter.rb b/app/models/queries/work_packages/filter/version_filter.rb index 72fc63e84b6..40e284adfe3 100644 --- a/app/models/queries/work_packages/filter/version_filter.rb +++ b/app/models/queries/work_packages/filter/version_filter.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -45,7 +46,7 @@ class Queries::WorkPackages::Filter::VersionFilter < end def human_name - WorkPackage.human_attribute_name('fixed_version_id') + WorkPackage.human_attribute_name('fixed_version') end def self.key diff --git a/app/models/type/attributes.rb b/app/models/type/attributes.rb index 82426dcb30f..7dccf61bbbc 100644 --- a/app/models/type/attributes.rb +++ b/app/models/type/attributes.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) @@ -63,30 +64,54 @@ module Type::Attributes OpenProject::Cache.fetch('all_work_package_form_attributes', *WorkPackageCustomField.pluck('max(updated_at), count(id)').flatten, merge_date) do - rattrs = API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter.representable_attrs - definitions = rattrs[:definitions] - skip = ['_type', '_dependencies', 'attribute_groups', 'links', 'parent_id', 'parent', 'description'] - attributes = definitions.keys - .reject { |key| skip.include?(key) || definitions[key][:required] } - .map { |key| [key, JSON::parse(definitions[key].to_json)] }.to_h + calculate_all_work_package_form_attributes(merge_date) + end + end - # within the form date is shown as a single entry including start and due - if merge_date - attributes['date'] = { required: false, has_default: false } - attributes.delete 'due_date' - attributes.delete 'start_date' - end + private - WorkPackageCustomField.includes(:custom_options).all.each do |field| - attributes["custom_field_#{field.id}"] = { - required: field.is_required, - has_default: field.default_value.present?, - is_cf: true, - display_name: field.name - } - end + def calculate_all_work_package_form_attributes(merge_date) + attributes = calculate_default_work_package_form_attributes - attributes + # within the form date is shown as a single entry including start and due + if merge_date + merge_date_for_form_attributes(attributes) + end + + add_custom_fields_to_form_attributes(attributes) + + attributes + end + + def calculate_default_work_package_form_attributes + representable_config = API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter + .representable_attrs + + # For reasons beyond me, Representable::Config contains the definitions + # * nested in [:definitions] in some envs, e.g. development + # * directly in other envs, e.g. test + definitions = representable_config.key?(:definitions) ? representable_config[:definitions] : representable_config + + skip = ['_type', '_dependencies', 'attribute_groups', 'links', 'parent_id', 'parent', 'description'] + definitions.keys + .reject { |key| skip.include?(key) || definitions[key][:required] } + .map { |key| [key, JSON::parse(definitions[key].to_json)] }.to_h + end + + def merge_date_for_form_attributes(attributes) + attributes['date'] = { required: false, has_default: false } + attributes.delete 'due_date' + attributes.delete 'start_date' + end + + def add_custom_fields_to_form_attributes(attributes) + WorkPackageCustomField.includes(:custom_options).all.each do |field| + attributes["custom_field_#{field.id}"] = { + required: field.is_required, + has_default: field.default_value.present?, + is_cf: true, + display_name: field.name + } end end end @@ -146,7 +171,6 @@ module Type::Attributes # If a project context is given, that context is passed # to the constraint validator. def passes_attribute_constraint?(attribute, project: nil) - # Check custom field constraints if custom_field?(attribute) && !project.nil? return custom_field_in_project?(attribute, project) diff --git a/app/services/relations/create_relation_service.rb b/app/services/relations/create_relation_service.rb index 9a0f4967cd3..176090206d7 100644 --- a/app/services/relations/create_relation_service.rb +++ b/app/services/relations/create_relation_service.rb @@ -1,4 +1,5 @@ #-- encoding: UTF-8 + #-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2017 the OpenProject Foundation (OPF) diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index 45b1fb80052..a3385b15b76 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -68,7 +68,11 @@ See doc/COPYRIGHT.rdoc for more details. <% else %> <% unless @auth_sources.empty? || OpenProject::Configuration.disable_password_login? %> -
<%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }) %>
+
+ <%= f.select :auth_source_id, + ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), + label: :'activerecord.attributes.user.auth_source' %> +
<% end %> <% if !OpenProject::Configuration.disable_password_login? %> <% diff --git a/app/views/users/_simple_form.html.erb b/app/views/users/_simple_form.html.erb index 9643a8cc8dd..8601edfb217 100644 --- a/app/views/users/_simple_form.html.erb +++ b/app/views/users/_simple_form.html.erb @@ -45,7 +45,7 @@ See doc/COPYRIGHT.rdoc for more details. <% unless @auth_sources.empty? || OpenProject::Configuration.disable_password_login? %>
<% sources = ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }) %> - <%= f.select :auth_source_id, sources %> + <%= f.select :auth_source_id, sources, label: :'activerecord.attributes.user.auth_source' %>