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' %>
diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb
index b8e51424665..3ff5c9096e5 100644
--- a/config/initializers/10-patches.rb
+++ b/config/initializers/10-patches.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -33,19 +34,9 @@ module ActiveRecord
class Base
include Redmine::I18n
- # Translate attribute names for validation errors display
def self.human_attribute_name(attr, options = {})
- options_with_raise = { raise: true, default: false }.merge options
attr = attr.to_s.gsub(/_id\z/, '')
- super(attr, options_with_raise)
- rescue I18n::MissingTranslationData => e
- included_in_general_attributes = I18n.t('attributes').keys.map(&:to_s).include? attr
- included_in_superclasses = ancestors.select { |a| a.ancestors.include? ActiveRecord::Base }.any? { |klass| !(I18n.t("activerecord.attributes.#{klass.name.underscore}.#{attr}").include? 'translation missing:') }
- unless included_in_general_attributes or included_in_superclasses
- # TODO: remove this method once no warning is displayed when running a server/console/tests/tasks etc.
- warn "[DEPRECATION] Relying on Redmine::I18n addition of `field_` to your translation key \"#{attr}\" on the \"#{self}\" model is deprecated. Please use proper ActiveRecord i18n! \n Caught: #{e.message}"
- end
- super(attr, options)
+ super
end
end
end
@@ -134,24 +125,6 @@ module ActiveModel
end
end
-require 'reform/contract'
-
-class Reform::Contract::Errors
- def merge_with_storing_error_symbols!(errors, prefix)
- @store_new_symbols = false
- merge_without_storing_error_symbols!(errors, prefix)
- @store_new_symbols = true
-
- errors.keys.each do |attribute|
- errors.symbols_and_messages_for(attribute).each do |symbol, full_message, partial_message|
- writable_symbols_and_messages_for(attribute) << [symbol, full_message, partial_message]
- end
- end
- end
-
- alias_method_chain :merge!, :storing_error_symbols
-end
-
module ActionView
module Helpers
module Tags
@@ -267,23 +240,5 @@ ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
end
end
-module ActiveRecord
- class Errors
- # def on_with_id_handling(attribute)
- # attribute = attribute.to_s
- # if attribute.ends_with? '_id'
- # on_without_id_handling(attribute) || on_without_id_handling(attribute[0..-4])
- # else
- # on_without_id_handling(attribute)
- # end
- # end
-
- # alias_method_chain :on, :id_handling
- end
-end
-
# Patch acts_as_list before any class includes the module
require 'open_project/patches/acts_as_list'
-
-# Backports some useful ruby 2.3 methods for Hash
-require 'open_project/patches/hash'
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index 0e93276d684..56e75000972 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -33,7 +33,7 @@
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone
-Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV)
+Mime::SET << Mime[:csv] unless Mime::SET.include?(Mime[:csv])
Mime::Type.register 'application/pdf', :pdf unless Mime::Type.lookup_by_extension(:pdf)
Mime::Type.register 'image/png', :png unless Mime::Type.lookup_by_extension(:png)
diff --git a/lib/open_project/patches/hash.rb b/config/initializers/reform.rb
similarity index 71%
rename from lib/open_project/patches/hash.rb
rename to config/initializers/reform.rb
index 5463191c06d..be10a067143 100644
--- a/lib/open_project/patches/hash.rb
+++ b/config/initializers/reform.rb
@@ -1,3 +1,5 @@
+#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -26,24 +28,20 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-module OpenProject
- module Patches
- module Hash
- ##
- # Becomes obsolete with ruby 2.3's Hash#dig but until then this will do.
- def dig(*keys)
- keys.inject(self) { |hash, key| hash && (hash.is_a?(Hash) || nil) && hash[key] }
- end
+require "reform/form/active_model/validations"
- def map_values(&_block)
- entries = map { |key, value| [key, (yield value)] }
-
- ::Hash[entries]
- end
- end
- end
+Reform::Form.class_eval do
+ include Reform::Form::ActiveModel::Validations
end
-if !Hash.instance_methods.include? :dig
- Hash.prepend OpenProject::Patches::Hash
+Reform::Contract.class_eval do
+ include Reform::Form::ActiveModel::Validations
+end
+
+require 'reform/contract'
+
+require 'open_project/patches/reform'
+
+class Reform::Form::ActiveModel::Errors
+ prepend OpenProject::Patches::Reform
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9f863142ec0..99097727e9b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -253,13 +253,13 @@ en:
activemodel:
errors:
models:
- "queries/base_contract":
+ query:
attributes:
project:
error_not_found: "not found"
public:
error_unauthorized: "- The user has no permission to create public queries."
- "relations/base_contract":
+ relation:
attributes:
to:
error_not_found: "work package in `to` position not found or not visible"
@@ -267,7 +267,7 @@ en:
from:
error_not_found: "work package in `from` position not found or not visible"
error_readonly: "an existing relation's `from` link is immutable"
- "users/base_contract":
+ user:
attributes:
status:
invalid_on_create: "is not a valid status for new users."
diff --git a/docs/api/apiv3/endpoints/work-packages.apib b/docs/api/apiv3/endpoints/work-packages.apib
index ad27ca6fb38..fc635a1a6a8 100644
--- a/docs/api/apiv3/endpoints/work-packages.apib
+++ b/docs/api/apiv3/endpoints/work-packages.apib
@@ -17,27 +17,28 @@
## Linked Properties
-| Link | Description | Type | Constraints | Supported operations | Condition |
-| :----------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------ | --------------------- | ----------------------------------------- |
-| self | This work package | WorkPackage | not null | READ | |
-| schema | The schema of this work package | Schema | not null | READ | |
-| ancestors | Array of all visible ancestors of the work package, with the root node being the first element | Collection | not null | READ | **Permission** view work packages |
-| attachments | The files attached to this work package | Collection | not null | READ | |
-| author | The person that created the work package | User | not null | READ | |
-| assignee | The person that is intended to work on the work package | User | | READ / WRITE | |
-| availableWatchers | All users that can be added to the work package as watchers. | User | | READ | **Permission** add work package watchers |
-| category | The category of the work package | Category | | READ / WRITE | |
-| children | Array of all visible children of the work package | Collection | not null | READ | **Permission** view work packages |
-| priority | The priority of the work package | Priority | not null | READ / WRITE | |
-| project | The project to which the work package belongs | Project | not null | READ / WRITE | |
-| responsible | The person that is responsible for the overall outcome | User | | READ / WRITE | |
-| relations | Relations this work package is involved in | Relation | | READ | **Permission** view work packages |
-| revisions | Revisions that are referencing the work package | Revision | | READ | **Permission** view changesets |
-| status | The current status of the work package | Status | not null | READ / WRITE | |
-| timeEntries | All time entries logged on the work package. Please note that this is a link to an HTML resource for now and as such, the link is subject to change. | N/A | | READ | **Permission** view time entries |
-| type | The type of the work package | Type | not null | READ / WRITE | |
-| version | The version associated to the work package | Version | | READ / WRITE | |
-| watchers | All users that are currently watching this work package | Collection | | READ | **Permission** view work package watchers |
+| Link | Description | Type | Constraints | Supported operations | Condition |
+| :----------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------ | --------------------- | ----------------------------------------- |
+| self | This work package | WorkPackage | not null | READ | |
+| schema | The schema of this work package | Schema | not null | READ | |
+| ancestors | Array of all visible ancestors of the work package, with the root node being the first element | Collection | not null | READ | **Permission** view work packages |
+| attachments | The files attached to this work package | Collection | not null | READ | |
+| author | The person that created the work package | User | not null | READ | |
+| assignee | The person that is intended to work on the work package | User | | READ / WRITE | |
+| availableWatchers | All users that can be added to the work package as watchers. | User | | READ | **Permission** add work package watchers |
+| category | The category of the work package | Category | | READ / WRITE | |
+| children | Array of all visible children of the work package | Collection | not null | READ | **Permission** view work packages |
+| parent | Parent work package | WorkPackage | Needs to be visible (to the current user) | READ / WRITE | |
+| priority | The priority of the work package | Priority | not null | READ / WRITE | |
+| project | The project to which the work package belongs | Project | not null | READ / WRITE | |
+| responsible | The person that is responsible for the overall outcome | User | | READ / WRITE | |
+| relations | Relations this work package is involved in | Relation | | READ | **Permission** view work packages |
+| revisions | Revisions that are referencing the work package | Revision | | READ | **Permission** view changesets |
+| status | The current status of the work package | Status | not null | READ / WRITE | |
+| timeEntries | All time entries logged on the work package. Please note that this is a link to an HTML resource for now and as such, the link is subject to change. | N/A | | READ | **Permission** view time entries |
+| type | The type of the work package | Type | not null | READ / WRITE | |
+| version | The version associated to the work package | Version | | READ / WRITE | |
+| watchers | All users that are currently watching this work package | Collection | | READ | **Permission** view work package watchers |
## Local Properties
@@ -48,7 +49,6 @@
| subject | Work package subject | String | not null; 1 <= length <= 255 | READ / WRITE | |
| type | Name of the work package's type | String | not null | READ | |
| description | The work package description | Formattable | | READ / WRITE | |
-| parentId | Parent work package id | Integer | Must be an id of an existing and visible (for the current user) work package | READ / WRITE | |
| startDate | Scheduled beginning of a work package | Date | Cannot be set for parent work packages; must be equal or greater than the earliest possible start date; Exists only on work packages of a non milestone type | READ / WRITE | |
| dueDate | Scheduled end of a work package | Date | Cannot be set for parent work packages; must be greater than or equal to the start date; Exists only on work packages of a non milestone type | READ / WRITE | |
| date | Date on which a milestone is achieved | Date | Exists only on work packages of a milestone type
diff --git a/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts b/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts
index 6477f5c58ac..90ba47c8e6e 100644
--- a/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts
+++ b/frontend/app/components/api/api-v3/hal-resources/work-package-resource.service.ts
@@ -103,6 +103,7 @@ var schemaCacheService: SchemaCacheService;
var NotificationsService: any;
var wpNotificationsService: any;
var AttachmentCollectionResource:any;
+var v3Path:any;
export class WorkPackageResource extends HalResource {
// Add index signature for getter this[attr]
@@ -134,7 +135,6 @@ export class WorkPackageResource extends HalResource {
public $embedded: WorkPackageResourceEmbedded;
public $links: WorkPackageLinksObject;
public $pristine: { [attribute: string]: any } = {};
- public parentId: number;
public subject: string;
public updatedAt: Date;
public lockVersion: number;
@@ -585,7 +585,15 @@ export class WorkPackageResource extends HalResource {
return apiWorkPackages.createWorkPackage(payload);
};
- this.parentId = this.parentId || $stateParams.parent_id;
+ if (this.parent) {
+ this.$source._links['parent'] = {
+ href: this.parent.href
+ };
+ } else if ($stateParams.parent_id) {
+ this.$source._links['parent'] = {
+ href: v3Path.wp({ wp: $stateParams.parent_id })
+ };
+ }
}
/**
@@ -636,7 +644,8 @@ function wpResource(...args:any[]) {
schemaCacheService,
NotificationsService,
wpNotificationsService,
- AttachmentCollectionResource] = args;
+ AttachmentCollectionResource,
+ v3Path] = args;
return WorkPackageResource;
}
@@ -651,7 +660,8 @@ wpResource.$inject = [
'schemaCacheService',
'NotificationsService',
'wpNotificationsService',
- 'AttachmentCollectionResource'
+ 'AttachmentCollectionResource',
+ 'v3Path'
];
opApiModule.factory('WorkPackageResource', wpResource);
diff --git a/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts b/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts
index 22ae0d074dd..72206af5a1f 100644
--- a/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts
+++ b/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts
@@ -81,22 +81,22 @@ export class HierarchyRenderPass extends TableRenderPass {
* @returns {boolean}
*/
public deferInsertion(workPackage:WorkPackageResourceInterface):boolean {
- const parentId = workPackage.parentId;
+ const parent = workPackage.parent;
// Will only defer if parent exists
- if (!parentId) {
+ if (!parent) {
return false;
}
// Will only defer is parent is
// 1. existent in the table results
// 1. yet to be rendered
- if (this.workPackageTable.rowIndex[parentId] === undefined || this.rendered[parentId]) {
+ if (this.workPackageTable.rowIndex[parent.id] === undefined || this.rendered[parent.id]) {
return false;
}
- const elements = this.deferred[parentId] || [];
- this.deferred[parentId] = elements.concat([workPackage]);
+ const elements = this.deferred[parent.id] || [];
+ this.deferred[parent.id] = elements.concat([workPackage]);
return true;
}
@@ -115,7 +115,7 @@ export class HierarchyRenderPass extends TableRenderPass {
deferredChildren.forEach((child:WorkPackageResourceInterface) => {
// Callback on the child itself
const row:WorkPackageTableRow = this.workPackageTable.rowIndex[child.id];
- this.insertUnderParent(row, child.parentId.toString());
+ this.insertUnderParent(row, child.parent);
// Descend into any children the child WP might have and callback
this.renderAllDeferredChildren(child);
@@ -145,7 +145,7 @@ export class HierarchyRenderPass extends TableRenderPass {
} else {
// This ancestor must be inserted in the last position of its root
const parent = ancestors[index - 1];
- this.insertAtExistingHierarchy(ancestor, ancestorRow, parent.id, hidden, true);
+ this.insertAtExistingHierarchy(ancestor, ancestorRow, parent, hidden, true);
}
// Remember we just added this extra ancestor row
@@ -161,7 +161,7 @@ export class HierarchyRenderPass extends TableRenderPass {
// Insert this row to parent
const parent = _.last(ancestors);
- this.insertUnderParent(row, parent.id);
+ this.insertUnderParent(row, parent);
}
/**
@@ -169,10 +169,10 @@ export class HierarchyRenderPass extends TableRenderPass {
* @param row
* @param parentId
*/
- private insertUnderParent(row:WorkPackageTableRow, parentId:string) {
+ private insertUnderParent(row:WorkPackageTableRow, parent:WorkPackageResourceInterface) {
const [tr, hidden] = this.rowBuilder.buildEmpty(row.object);
row.element = tr;
- this.insertAtExistingHierarchy(row.object, tr, parentId, hidden, false);
+ this.insertAtExistingHierarchy(row.object, tr, parent, hidden, false);
}
/**
@@ -210,11 +210,15 @@ export class HierarchyRenderPass extends TableRenderPass {
/**
* Append a row to the given parent hierarchy group.
*/
- private insertAtExistingHierarchy(workPackage:WorkPackageResourceInterface, el:HTMLElement, parentId:string, hidden:boolean, isAncestor:boolean) {
+ private insertAtExistingHierarchy(workPackage:WorkPackageResourceInterface,
+ el:HTMLElement,
+ parent:WorkPackageResourceInterface,
+ hidden:boolean,
+ isAncestor:boolean) {
// Either append to the hierarchy group root (= the parentID row itself)
- const hierarchyRoot = `.__hierarchy-root-${parentId}`;
+ const hierarchyRoot = `.__hierarchy-root-${parent.id}`;
// Or, if it has descendants, append to the LATEST of that set
- const hierarchyGroup = `.__hierarchy-group-${parentId}`;
+ const hierarchyGroup = `.__hierarchy-group-${parent.id}`;
// Insert into table
const target = jQuery(this.tableBody).find(`${hierarchyRoot},${hierarchyGroup}`).last();
diff --git a/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts b/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
index 554fc1df8f8..90e4e315fc6 100644
--- a/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
+++ b/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
@@ -32,24 +32,24 @@ import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-
import {WorkPackageCacheService} from "../../work-packages/work-package-cache.service";
export class WorkPackageRelationsHierarchyController {
- public workPackage: WorkPackageResourceInterface;
- public showEditForm: boolean = false;
+ public workPackage:WorkPackageResourceInterface;
+ public showEditForm:boolean = false;
public workPackagePath = this.PathHelper.workPackagePath;
public canHaveChildren = !this.workPackage.isMilestone;
public canModifyHierarchy = !!this.workPackage.changeParent;
public canAddRelation = !!this.workPackage.addRelation;
- constructor(protected $scope: ng.IScope,
- protected $rootScope: ng.IRootScopeService,
- protected $q: ng.IQService,
- protected wpCacheService: WorkPackageCacheService,
- protected PathHelper: op.PathHelper,
- protected I18n: op.I18n) {
+ constructor(protected $scope:ng.IScope,
+ protected $rootScope:ng.IRootScopeService,
+ protected $q:ng.IQService,
+ protected wpCacheService:WorkPackageCacheService,
+ protected PathHelper:op.PathHelper,
+ protected I18n:op.I18n) {
scopedObservable(
this.$scope,
this.wpCacheService.loadWorkPackage(this.workPackage.id).values$())
- .subscribe((wp: WorkPackageResourceInterface) => {
+ .subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.loadParent();
this.loadChildren();
@@ -67,15 +67,15 @@ export class WorkPackageRelationsHierarchyController {
}
protected loadParent() {
- if (!angular.isNumber(this.workPackage.parentId)) {
+ if (!this.workPackage.parent) {
return;
}
scopedObservable(
this.$scope,
- this.wpCacheService.loadWorkPackage(this.workPackage.parentId.toString()).values$())
+ this.wpCacheService.loadWorkPackage(this.workPackage.parent.id).values$())
.take(1)
- .subscribe((parent: WorkPackageResourceInterface) => {
+ .subscribe((parent:WorkPackageResourceInterface) => {
this.workPackage.parent = parent;
});
}
diff --git a/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts b/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts
index 811a0cd1a38..d9e37b3a331 100644
--- a/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts
+++ b/frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts
@@ -40,17 +40,33 @@ export class WorkPackageRelationsHierarchyService {
protected wpTableRefresh: WorkPackageTableRefreshService,
protected $rootScope: ng.IRootScopeService,
protected wpNotificationsService: WorkPackageNotificationService,
- protected wpCacheService: WorkPackageCacheService) {
+ protected wpCacheService: WorkPackageCacheService,
+ protected v3Path:any) {
}
- public changeParent(workPackage: WorkPackageResourceInterface, parentId: string | null) {
+ public changeParent(workPackage:WorkPackageResourceInterface, parentId:string | null) {
+ let payload:any = {
+ lockVersion: workPackage.lockVersion
+ };
+
+ if (parentId) {
+ payload['_links'] = {
+ parent: {
+ href: this.v3Path.wp({wp: parentId})
+ }
+ };
+ } else {
+ payload['_links'] = {
+ parent: {
+ href: null
+ }
+ };
+ }
+
return workPackage
- .changeParent({
- parentId: parentId,
- lockVersion: workPackage.lockVersion
- })
- .then((wp: WorkPackageResourceInterface) => {
+ .changeParent(payload)
+ .then((wp:WorkPackageResourceInterface) => {
this.wpCacheService.updateWorkPackage(wp);
this.wpNotificationsService.showSave(wp);
this.wpTableRefresh.request(true, `Changed parent of ${workPackage.id} to ${parentId}`);
@@ -96,10 +112,14 @@ export class WorkPackageRelationsHierarchyService {
});
}
- public removeChild(childWorkPackage: WorkPackageResourceInterface) {
+ public removeChild(childWorkPackage:WorkPackageResourceInterface) {
return childWorkPackage.$load().then(() => {
return childWorkPackage.changeParent({
- parentId: null,
+ _links: {
+ parent: {
+ href: null
+ }
+ },
lockVersion: childWorkPackage.lockVersion
}).then(wp => {
this.wpCacheService.updateWorkPackage(wp);
diff --git a/lib/api/decorators/aggregation_group.rb b/lib/api/decorators/aggregation_group.rb
index aeab756836c..67d13a3d836 100644
--- a/lib/api/decorators/aggregation_group.rb
+++ b/lib/api/decorators/aggregation_group.rb
@@ -88,8 +88,6 @@ module API
false
end
- private
-
attr_reader :sums,
:count,
:query
diff --git a/lib/api/decorators/collection.rb b/lib/api/decorators/collection.rb
index 33c63aeab04..8bfbe2ad3f7 100644
--- a/lib/api/decorators/collection.rb
+++ b/lib/api/decorators/collection.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -53,20 +54,18 @@ module API
{ href: @self_link }
end
- property :total, getter: -> (*) { @total }, exec_context: :decorator
- property :count, getter: -> (*) { count }
+ property :total, getter: ->(*) { @total }, exec_context: :decorator
+ property :count, getter: ->(*) { count }
collection :elements,
- getter: -> (*) {
- represented.map { |model|
+ getter: ->(*) {
+ represented.map do |model|
element_decorator.create(model, current_user: current_user)
- }
+ end
},
exec_context: :decorator,
embedded: true
- private
-
def _type
'Collection'
end
diff --git a/lib/api/decorators/link_object.rb b/lib/api/decorators/link_object.rb
index b1f113c2e67..4ec803404e0 100644
--- a/lib/api/decorators/link_object.rb
+++ b/lib/api/decorators/link_object.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,7 @@ module API
path: :"#{property_name}",
namespace: path.to_s.pluralize,
getter: :"#{property_name}_id",
- title_getter: -> (*) { model.send(property_name).name },
+ title_getter: ->(*) { model.send(property_name).name },
setter: :"#{getter}=")
@property_name = property_name
@path = path
@@ -49,36 +50,40 @@ module API
property :href,
exec_context: :decorator,
- getter: -> (*) {
- id = represented.send(@getter) if represented
-
- return nil if id.nil? || id == 0
-
- api_v3_paths.send(@path, id)
- },
- setter: -> (value, *) {
- if value
- id = ::API::Utilities::ResourceLinkParser.parse_id value,
- property: @property_name,
- expected_version: '3',
- expected_namespace: @namespace
- end
-
- represented.send(@setter, id)
- },
render_nil: true
property :title,
exec_context: :decorator,
- getter: -> (*) {
- attribute = ::API::Utilities::PropertyNameConverter.to_ar_name(
- @property_name,
- context: represented
- )
- represented.try(attribute).try(:name)
- },
writeable: false,
render_nil: false
+
+ def href
+ id = represented.send(@getter) if represented
+
+ return nil if id.nil? || id.zero?
+
+ api_v3_paths.send(@path, id)
+ end
+
+ def href=(value)
+ if value
+ id = ::API::Utilities::ResourceLinkParser.parse_id value,
+ property: @property_name,
+ expected_version: '3',
+ expected_namespace: @namespace
+ end
+
+ represented.send(@setter, id)
+ end
+
+ def title
+ attribute = ::API::Utilities::PropertyNameConverter.to_ar_name(
+ @property_name,
+ context: represented
+ )
+
+ represented.try(attribute).try(:name)
+ end
end
end
end
diff --git a/lib/api/decorators/schema_representer.rb b/lib/api/decorators/schema_representer.rb
index 55a2f119f47..90e19687f1a 100644
--- a/lib/api/decorators/schema_representer.rb
+++ b/lib/api/decorators/schema_representer.rb
@@ -216,8 +216,6 @@ module API
property :_dependencies,
exec_context: :decorator
- private
-
attr_accessor :form_embedded,
:self_link
diff --git a/lib/api/decorators/single.rb b/lib/api/decorators/single.rb
index 0a493833389..2fd3100403b 100644
--- a/lib/api/decorators/single.rb
+++ b/lib/api/decorators/single.rb
@@ -111,7 +111,7 @@ module API
::API::Utilities::DecoratorFactory.new(decorator: decorator,
current_user: current_user)
},
- if: -> (*) { embed_links && call_or_use(show_if) }
+ if: ->(*) { embed_links && call_or_use(show_if) }
end
class_attribute :to_eager_load
@@ -121,7 +121,8 @@ module API
current_user.allowed_to?(permission, context)
end
- private
+ # Override in subclasses to specify the JSON indicated "_type" of this representer
+ def _type; end
def call_or_send_to_represented(callable_or_name)
if callable_or_name.respond_to? :call
@@ -143,9 +144,6 @@ module API
::API::V3::Utilities::DateTimeFormatter
end
- # Override in subclasses to specify the JSON indicated "_type" of this representer
- def _type; end
-
# If a subclass does not depend on a model being passed to this class, it can override
# this method and return false. Otherwise it will be enforced that the model of each
# representer is non-nil.
diff --git a/lib/api/v3/attachments/attachment_metadata_representer.rb b/lib/api/v3/attachments/attachment_metadata_representer.rb
index 577e08253b3..2af75ebcf61 100644
--- a/lib/api/v3/attachments/attachment_metadata_representer.rb
+++ b/lib/api/v3/attachments/attachment_metadata_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -40,10 +41,10 @@ module API
property :file_name
property :description,
- getter: -> (*) {
+ getter: ->(*) {
::API::Decorators::Formattable.new(description, format: 'plain')
},
- setter: -> (value, *) { self.description = value['raw'] },
+ setter: ->(fragment:, **) { self.description = fragment['raw'] },
render_nil: true
end
end
diff --git a/lib/api/v3/configuration/configuration_representer.rb b/lib/api/v3/configuration/configuration_representer.rb
index 84cddcd164a..ea57f8b9e2e 100644
--- a/lib/api/v3/configuration/configuration_representer.rb
+++ b/lib/api/v3/configuration/configuration_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -40,12 +41,10 @@ module API
end
property :maximum_attachment_file_size,
- getter: -> (*) { attachment_max_size.to_i.kilobyte }
+ getter: ->(*) { attachment_max_size.to_i.kilobyte }
property :per_page_options,
- getter: -> (*) { per_page_options.split(',').map(&:to_i) }
-
- private
+ getter: ->(*) { per_page_options.split(',').map(&:to_i) }
def _type
'Configuration'
diff --git a/lib/api/v3/queries/columns/query_column_representer.rb b/lib/api/v3/queries/columns/query_column_representer.rb
index 10ca25e5b8e..0c30c33c5ac 100644
--- a/lib/api/v3/queries/columns/query_column_representer.rb
+++ b/lib/api/v3/queries/columns/query_column_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -45,8 +46,6 @@ module API
property :caption,
as: :name
- private
-
def converted_name
convert_attribute(represented.name)
end
diff --git a/lib/api/v3/queries/filters/query_filter_instance_representer.rb b/lib/api/v3/queries/filters/query_filter_instance_representer.rb
index 96831bb0ae8..99fed033596 100644
--- a/lib/api/v3/queries/filters/query_filter_instance_representer.rb
+++ b/lib/api/v3/queries/filters/query_filter_instance_representer.rb
@@ -76,7 +76,9 @@ module API
exec_context: :decorator,
show_nil: true
- private
+ def _type
+ "#{converted_name.camelize}QueryFilter"
+ end
def name
represented.human_name
@@ -97,10 +99,6 @@ module API
end
end
- def _type
- "#{converted_name.camelize}QueryFilter"
- end
-
def converted_name
convert_attribute(represented.name)
end
diff --git a/lib/api/v3/queries/filters/query_filter_representer.rb b/lib/api/v3/queries/filters/query_filter_representer.rb
index 85c93bd9066..80f33f82e6c 100644
--- a/lib/api/v3/queries/filters/query_filter_representer.rb
+++ b/lib/api/v3/queries/filters/query_filter_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -43,8 +44,6 @@ module API
property :id,
exec_context: :decorator
- private
-
def converted_key
convert_attribute(represented.name)
end
diff --git a/lib/api/v3/queries/group_bys/query_group_by_representer.rb b/lib/api/v3/queries/group_bys/query_group_by_representer.rb
index 65569eed55d..2b6da514c56 100644
--- a/lib/api/v3/queries/group_bys/query_group_by_representer.rb
+++ b/lib/api/v3/queries/group_bys/query_group_by_representer.rb
@@ -45,8 +45,6 @@ module API
property :caption,
as: :name
- private
-
def converted_name
convert_attribute(represented.name)
end
diff --git a/lib/api/v3/queries/operators/query_operator_representer.rb b/lib/api/v3/queries/operators/query_operator_representer.rb
index 930c93d89c4..98e46eaa78a 100644
--- a/lib/api/v3/queries/operators/query_operator_representer.rb
+++ b/lib/api/v3/queries/operators/query_operator_representer.rb
@@ -46,8 +46,6 @@ module API
property :name,
exec_context: :decorator
- private
-
def name
represented.human_name
end
diff --git a/lib/api/v3/queries/query_representer.rb b/lib/api/v3/queries/query_representer.rb
index ead89af1ba3..b0bc841a69e 100644
--- a/lib/api/v3/queries/query_representer.rb
+++ b/lib/api/v3/queries/query_representer.rb
@@ -217,12 +217,12 @@ module API
:user,
project: :work_package_custom_fields]
- private
-
def _type
'Query'
end
+ private
+
def allowed_to?(action)
@policy ||= QueryPolicy.new(current_user)
diff --git a/lib/api/v3/queries/schemas/query_filter_instance_schema_representer.rb b/lib/api/v3/queries/schemas/query_filter_instance_schema_representer.rb
index cc91b088997..97bfa43c709 100644
--- a/lib/api/v3/queries/schemas/query_filter_instance_schema_representer.rb
+++ b/lib/api/v3/queries/schemas/query_filter_instance_schema_representer.rb
@@ -101,8 +101,6 @@ module API
WorkPackage
end
- private
-
alias :filter :represented
def _type
diff --git a/lib/api/v3/queries/schemas/query_schema_representer.rb b/lib/api/v3/queries/schemas/query_schema_representer.rb
index 58d2a3784ae..697f298b4c6 100644
--- a/lib/api/v3/queries/schemas/query_schema_representer.rb
+++ b/lib/api/v3/queries/schemas/query_schema_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -196,8 +197,6 @@ module API
Query
end
- private
-
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
diff --git a/lib/api/v3/queries/sort_bys/query_sort_by_representer.rb b/lib/api/v3/queries/sort_bys/query_sort_by_representer.rb
index 8a2314eef0f..e75f96a0548 100644
--- a/lib/api/v3/queries/sort_bys/query_sort_by_representer.rb
+++ b/lib/api/v3/queries/sort_bys/query_sort_by_representer.rb
@@ -57,8 +57,6 @@ module API
property :name
- private
-
def self_link_params
[represented.converted_name, represented.direction_name]
end
diff --git a/lib/api/v3/relations/relations_api.rb b/lib/api/v3/relations/relations_api.rb
index 31867423a17..ba6baf222dd 100644
--- a/lib/api/v3/relations/relations_api.rb
+++ b/lib/api/v3/relations/relations_api.rb
@@ -66,7 +66,7 @@ module API
service = ::UpdateRelationService.new relation: Relation.find_by_id!(params[:id]),
user: current_user
call = service.call attributes: attributes,
- send_notifications: !(params[:notify] == 'false')
+ send_notifications: (params[:notify] != 'false')
if call.success?
representer.new call.result, current_user: current_user, embed_links: true
diff --git a/lib/api/v3/schemas/schema_dependency_representer.rb b/lib/api/v3/schemas/schema_dependency_representer.rb
index 098969b7384..6ddfdfe6a91 100644
--- a/lib/api/v3/schemas/schema_dependency_representer.rb
+++ b/lib/api/v3/schemas/schema_dependency_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -44,8 +45,6 @@ module API
current_user: current_user)
end
- private
-
attr_accessor :on
alias :dependencies :represented
diff --git a/lib/api/v3/string_objects/string_object_representer.rb b/lib/api/v3/string_objects/string_object_representer.rb
index 666cfeb7752..cab1974913b 100644
--- a/lib/api/v3/string_objects/string_object_representer.rb
+++ b/lib/api/v3/string_objects/string_object_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -56,8 +57,6 @@ module API
# (nil values are not supported by a string_objects URL anyway)
getter: ->(*) { values.last || '' }
- private
-
def _type
'StringObject'
end
diff --git a/lib/api/v3/users/user_representer.rb b/lib/api/v3/users/user_representer.rb
index 6aca4ff1b72..bc8e4ee10db 100644
--- a/lib/api/v3/users/user_representer.rb
+++ b/lib/api/v3/users/user_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -54,66 +55,70 @@ module API
end
link :updateImmediately do
+ next unless current_user_is_admin
{
href: api_v3_paths.user(represented.id),
title: "Update #{represented.login}",
method: :patch
- } if current_user_is_admin
+ }
end
link :lock do
+ next unless current_user_is_admin && represented.lockable?
{
href: api_v3_paths.user_lock(represented.id),
title: "Set lock on #{represented.login}",
method: :post
- } if current_user_is_admin && represented.lockable?
+ }
end
link :unlock do
+ next unless current_user_is_admin && represented.activatable?
{
href: api_v3_paths.user_lock(represented.id),
title: "Remove lock on #{represented.login}",
method: :delete
- } if current_user_is_admin && represented.activatable?
+ }
end
link :delete do
+ next unless current_user_can_delete_represented?
{
href: api_v3_paths.user(represented.id),
title: "Delete #{represented.login}",
method: :delete
- } if current_user_can_delete_represented?
+ }
end
property :id,
render_nil: true
property :login,
+ exec_context: :decorator,
render_nil: false,
getter: ->(*) { represented.login },
- setter: ->(value, *) { represented.login = value },
- exec_context: :decorator,
+ setter: ->(fragment:, represented:, **) { represented.login = fragment },
if: ->(*) { current_user_is_admin_or_self }
property :admin,
- render_nil: false,
exec_context: :decorator,
+ render_nil: false,
getter: ->(*) {
represented.admin?
},
- setter: ->(value, *) { represented.admin = value },
+ setter: ->(fragment:, represented:, **) { represented.admin = fragment },
if: ->(*) { current_user_is_admin }
property :subtype,
- getter: -> (*) { type },
+ getter: ->(*) { type },
render_nil: true
property :firstName,
- getter: ->(*) { represented.firstname },
- setter: ->(value, *) { represented.firstname = value },
exec_context: :decorator,
+ getter: ->(*) { represented.firstname },
+ setter: ->(fragment:, represented:, **) { represented.firstname = fragment },
render_nil: false,
if: ->(*) { current_user_is_admin_or_self }
property :lastName,
- getter: ->(*) { represented.lastname },
- setter: ->(value, *) { represented.lastname = value },
exec_context: :decorator,
+ getter: ->(*) { represented.lastname },
+ setter: ->(fragment:, represented:, **) { represented.lastname = fragment },
render_nil: false,
if: ->(*) { current_user_is_admin_or_self }
property :name,
@@ -128,48 +133,50 @@ module API
end
}
property :avatar,
- getter: -> (*) { avatar_url(represented) },
- render_nil: true,
- exec_context: :decorator
- property :created_on,
- as: 'createdAt',
exec_context: :decorator,
- getter: -> (*) { datetime_formatter.format_datetime(represented.created_on) },
+ getter: ->(*) { avatar_url(represented) },
+ render_nil: true
+ property :created_on,
+ exec_context: :decorator,
+ as: 'createdAt',
+ getter: ->(*) { datetime_formatter.format_datetime(represented.created_on) },
render_nil: false,
if: ->(*) { current_user_is_admin_or_self }
property :updated_on,
- as: 'updatedAt',
exec_context: :decorator,
- getter: -> (*) { datetime_formatter.format_datetime(represented.updated_on) },
+ as: 'updatedAt',
+ getter: ->(*) { datetime_formatter.format_datetime(represented.updated_on) },
render_nil: false,
if: ->(*) { current_user_is_admin_or_self }
property :status,
- getter: -> (*) { status_name },
- setter: -> (value, *) { self.status = User::STATUSES[value.to_sym] },
+ getter: ->(*) { status_name },
+ setter: ->(fragment:, represented:, **) { represented.status = User::STATUSES[fragment.to_sym] },
render_nil: true
link :auth_source do
+ next unless represented.is_a?(User) && represented.auth_source && current_user.admin?
+
{
href: "/api/v3/auth_sources/#{represented.auth_source_id}",
title: represented.auth_source.name
- } if represented.is_a?(User) && represented.auth_source && current_user.admin?
+ }
end
property :identity_url,
- as: 'identityUrl',
exec_context: :decorator,
- getter: -> (*) { represented.identity_url },
- setter: -> (value, *) { represented.identity_url = value },
+ as: 'identityUrl',
+ getter: ->(*) { represented.identity_url },
+ setter: ->(fragment:, represented:, **) { represented.identity_url = fragment },
render_nil: true,
if: ->(*) { represented.is_a?(User) && current_user_is_admin_or_self }
# Write-only properties
property :password,
- getter: -> (*) { nil },
+ getter: ->(*) { nil },
render_nil: false,
- setter: -> (value, *) {
- self.password = self.password_confirmation = value
+ setter: ->(fragment:, represented:, **) {
+ represented.password = represented.password_confirmation = fragment
}
##
diff --git a/lib/api/v3/utilities/custom_field_injector.rb b/lib/api/v3/utilities/custom_field_injector.rb
index 1e9b53f7982..a2ef69162e3 100644
--- a/lib/api/v3/utilities/custom_field_injector.rb
+++ b/lib/api/v3/utilities/custom_field_injector.rb
@@ -271,8 +271,8 @@ module API
end
def link_value_setter_for(custom_field, property, expected_namespace)
- ->(link_object, *) {
- values = Array([link_object].flatten).flat_map do |link|
+ ->(fragment:, represented:, **) {
+ values = Array([fragment].flatten).flat_map do |link|
href = link['href']
value =
if href
@@ -317,7 +317,8 @@ module API
end
def inject_property_value(custom_field)
- @class.property property_name(custom_field.id),
+ @class.property "custom_field_#{custom_field.id}".to_sym,
+ as: property_name(custom_field.id),
getter: property_value_getter_for(custom_field),
setter: property_value_setter_for(custom_field),
render_nil: true
@@ -336,8 +337,12 @@ module API
end
def property_value_setter_for(custom_field)
- ->(value, *) {
- value = value['raw'] if custom_field.field_format == 'text'
+ ->(fragment:, **) {
+ value = if custom_field.field_format == 'text'
+ fragment['raw']
+ else
+ fragment
+ end
self.custom_field_values = { custom_field.id => value }
}
end
diff --git a/lib/api/v3/watchers/watcher_representer.rb b/lib/api/v3/watchers/watcher_representer.rb
index 2377e6a782a..d8888951b7a 100644
--- a/lib/api/v3/watchers/watcher_representer.rb
+++ b/lib/api/v3/watchers/watcher_representer.rb
@@ -40,12 +40,12 @@ module API
property :user,
exec_context: :decorator,
- getter: -> (*) {
+ getter: ->(*) {
create_link_representer
},
- setter: -> (value, *) {
+ setter: ->(fragment:, **) {
link = create_link_representer
- link.from_hash(value)
+ link.from_hash(fragment)
}
private
diff --git a/lib/api/v3/work_packages/create_work_packages.rb b/lib/api/v3/work_packages/create_work_packages.rb
index 4b8cadef68d..32212117832 100644
--- a/lib/api/v3/work_packages/create_work_packages.rb
+++ b/lib/api/v3/work_packages/create_work_packages.rb
@@ -41,7 +41,6 @@ module API
work_package = write_work_package_attributes(work_package, request_body || {})
-
result = create_work_package(current_user,
work_package,
notify_according_to_params)
diff --git a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb
index c00375a4ef7..e9cc3efa8e5 100644
--- a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb
+++ b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb
@@ -122,8 +122,8 @@ module API
schema :lock_version,
type: 'Integer',
- name_source: -> (*) { I18n.t('api_v3.attributes.lock_version') },
- show_if: -> (*) { @show_lock_version }
+ name_source: ->(*) { I18n.t('api_v3.attributes.lock_version') },
+ show_if: ->(*) { @show_lock_version }
schema :id,
type: 'Integer'
@@ -140,17 +140,17 @@ module API
schema :start_date,
type: 'Date',
required: false,
- show_if: -> (*) { !represented.milestone? }
+ show_if: ->(*) { !represented.milestone? }
schema :due_date,
type: 'Date',
required: false,
- show_if: -> (*) { !represented.milestone? }
+ show_if: ->(*) { !represented.milestone? }
schema :date,
type: 'Date',
required: false,
- show_if: -> (*) { represented.milestone? }
+ show_if: ->(*) { represented.milestone? }
schema :estimated_time,
type: 'Duration',
@@ -163,7 +163,7 @@ module API
schema :percentage_done,
type: 'Integer',
name_source: :done_ratio,
- show_if: -> (*) { Setting.work_package_done_ratio != 'disabled' },
+ show_if: ->(*) { Setting.work_package_done_ratio != 'disabled' },
required: false
schema :created_at,
@@ -178,7 +178,7 @@ module API
schema_with_allowed_link :project,
type: 'Project',
required: true,
- href_callback: -> (*) {
+ href_callback: ->(*) {
if @action == :create
api_v3_paths.available_projects_on_create
else
@@ -186,13 +186,7 @@ module API
end
}
- schema :parent_id,
- type: 'Integer',
- required: false,
- writable: true
-
# TODO:
- # * remove parent_id above in favor of only having :parent
# * create an available_work_package_parent resource
# * turn :parent into a schema_with_allowed_link
@@ -204,7 +198,7 @@ module API
schema_with_allowed_link :assignee,
type: 'User',
required: false,
- href_callback: -> (*) {
+ href_callback: ->(*) {
if represented.project
api_v3_paths.available_assignees(represented.project_id)
end
@@ -213,7 +207,7 @@ module API
schema_with_allowed_link :responsible,
type: 'User',
required: false,
- href_callback: -> (*) {
+ href_callback: ->(*) {
if represented.project
api_v3_paths.available_responsibles(represented.project_id)
end
@@ -221,7 +215,7 @@ module API
schema_with_allowed_collection :type,
value_representer: Types::TypeRepresenter,
- link_factory: -> (type) {
+ link_factory: ->(type) {
{
href: api_v3_paths.type(type.id),
title: type.name
@@ -231,7 +225,7 @@ module API
schema_with_allowed_collection :status,
value_representer: Statuses::StatusRepresenter,
- link_factory: -> (status) {
+ link_factory: ->(status) {
{
href: api_v3_paths.status(status.id),
title: status.name
@@ -241,7 +235,7 @@ module API
schema_with_allowed_collection :category,
value_representer: Categories::CategoryRepresenter,
- link_factory: -> (category) {
+ link_factory: ->(category) {
{
href: api_v3_paths.category(category.id),
title: category.name
@@ -251,7 +245,7 @@ module API
schema_with_allowed_collection :version,
value_representer: Versions::VersionRepresenter,
- link_factory: -> (version) {
+ link_factory: ->(version) {
{
href: api_v3_paths.version(version.id),
title: version.name
@@ -261,7 +255,7 @@ module API
schema_with_allowed_collection :priority,
value_representer: Priorities::PriorityRepresenter,
- link_factory: -> (priority) {
+ link_factory: ->(priority) {
{
href: api_v3_paths.priority(priority.id),
title: priority.name
diff --git a/lib/api/v3/work_packages/work_package_attribute_links_representer.rb b/lib/api/v3/work_packages/work_package_attribute_links_representer.rb
index 74bf39e2947..70258d43c40 100644
--- a/lib/api/v3/work_packages/work_package_attribute_links_representer.rb
+++ b/lib/api/v3/work_packages/work_package_attribute_links_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -41,9 +42,9 @@ module API
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
- injector_class.create_value_representer_for_link_patching(
- work_package,
- WorkPackageAttributeLinksRepresenter)
+ injector_class
+ .create_value_representer_for_link_patching(work_package,
+ WorkPackageAttributeLinksRepresenter)
end
def create(work_package)
@@ -60,21 +61,21 @@ module API
show_if: true)
property property,
- exec_context: :decorator,
- getter: -> (*) {
+ getter: ->(represented:, **) {
::API::Decorators::LinkObject.new(represented,
property_name: property,
path: path,
namespace: namespace,
getter: association)
},
- setter: -> (value, *) {
+ setter: ->(fragment:, represented:, **) {
link = ::API::Decorators::LinkObject.new(represented,
property_name: property,
path: path,
namespace: namespace,
getter: association)
- link.from_hash(value)
+
+ link.from_hash(fragment)
},
if: show_if
end
diff --git a/lib/api/v3/work_packages/work_package_collection_representer.rb b/lib/api/v3/work_packages/work_package_collection_representer.rb
index 60f251eddd6..bc5ddd60f8c 100644
--- a/lib/api/v3/work_packages/work_package_collection_representer.rb
+++ b/lib/api/v3/work_packages/work_package_collection_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -147,15 +148,13 @@ module API
property :total_sums,
exec_context: :decorator,
- getter: -> (*) {
+ getter: ->(*) {
if total_sums
::API::V3::WorkPackages::WorkPackageSumsRepresenter.create(total_sums)
end
},
render_nil: false
- private
-
def current_user_allowed_to_add_work_packages?
current_user.allowed_to?(:add_work_packages, project, global: project.nil?)
end
diff --git a/lib/api/v3/work_packages/work_package_payload_representer.rb b/lib/api/v3/work_packages/work_package_payload_representer.rb
index 2c47641d55e..d130e8fb192 100644
--- a/lib/api/v3/work_packages/work_package_payload_representer.rb
+++ b/lib/api/v3/work_packages/work_package_payload_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -40,9 +41,9 @@ module API
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
- injector_class.create_value_representer_for_property_patching(
- work_package,
- WorkPackagePayloadRepresenter)
+ injector_class
+ .create_value_representer_for_property_patching(work_package,
+ WorkPackagePayloadRepresenter)
end
def create(work_package)
@@ -58,101 +59,111 @@ module API
property :linked_resources,
as: :_links,
- exec_context: :decorator,
- getter: -> (*) {
- work_package_attribute_links_representer represented
- },
- setter: -> (value, *) {
- representer = work_package_attribute_links_representer represented
- representer.from_json(value.to_json)
- }
+ exec_context: :decorator
property :lock_version,
render_nil: true,
- getter: -> (*) {
+ getter: ->(*) {
lock_version.to_i
}
- property :subject, render_nil: true
+ property :subject,
+ render_nil: true
+
property :done_ratio,
as: :percentageDone,
render_nil: true,
- if: -> (*) { Setting.work_package_done_ratio == 'field' }
+ if: ->(*) { Setting.work_package_done_ratio == 'field' }
property :estimated_hours,
as: :estimatedTime,
exec_context: :decorator,
- getter: -> (*) {
- datetime_formatter.format_duration_from_hours(represented.estimated_hours,
- allow_nil: true)
- },
- setter: -> (value, *) {
- represented.estimated_hours = datetime_formatter.parse_duration_to_hours(
- value,
- 'estimated_hours',
- allow_nil: true)
- },
render_nil: true
property :description,
exec_context: :decorator,
- getter: -> (*) {
- API::Decorators::Formattable.new(represented.description, object: represented)
- },
- setter: -> (value, *) { represented.description = value['raw'] },
- render_nil: true
-
- property :parent_id,
- writeable: true,
render_nil: true
property :start_date,
exec_context: :decorator,
- getter: -> (*) {
- datetime_formatter.format_date(represented.start_date, allow_nil: true)
- },
- setter: -> (value, *) {
- represented.start_date = datetime_formatter.parse_date(value,
- 'startDate',
- allow_nil: true)
- },
render_nil: true,
- if: -> (*) { !represented.milestone? }
+ if: ->(*) { !represented.milestone? }
+
property :due_date,
exec_context: :decorator,
- getter: -> (*) {
- datetime_formatter.format_date(represented.due_date, allow_nil: true)
- },
- setter: -> (value, *) {
- represented.due_date = datetime_formatter.parse_date(value,
- 'dueDate',
- allow_nil: true)
- },
render_nil: true,
- if: -> (*) { !represented.milestone? }
+ if: ->(*) { !represented.milestone? }
+
property :date,
exec_context: :decorator,
- getter: -> (*) {
- datetime_formatter.format_date(represented.due_date, allow_nil: true)
- },
- setter: -> (value, *) {
- new_date = datetime_formatter.parse_date(value,
- 'date',
- allow_nil: true)
-
- represented.due_date = represented.start_date = new_date
- },
render_nil: true,
- if: -> (*) { represented.milestone? }
- property :version_id,
- getter: -> (*) { nil },
- setter: -> (value, *) { self.fixed_version_id = value },
- render_nil: false
- property :created_at,
- getter: -> (*) { nil }, render_nil: false
- property :updated_at,
- getter: -> (*) { nil }, render_nil: false
+ if: ->(represented:, **) { represented.milestone? }
- private
+ property :created_at,
+ getter: ->(*) { nil }, render_nil: false
+
+ property :updated_at,
+ getter: ->(*) { nil }, render_nil: false
+
+ def linked_resources
+ work_package_attribute_links_representer represented
+ end
+
+ def linked_resources=(value)
+ representer = work_package_attribute_links_representer represented
+ representer.from_json(value.to_json)
+ end
+
+ def estimated_hours
+ datetime_formatter.format_duration_from_hours(represented.estimated_hours,
+ allow_nil: true)
+ end
+
+ def estimated_hours=(value)
+ represented.estimated_hours = datetime_formatter
+ .parse_duration_to_hours(value,
+ 'estimated_hours',
+ allow_nil: true)
+ end
+
+ def description
+ API::Decorators::Formattable.new(represented.description, object: represented)
+ end
+
+ def description=(value)
+ represented.description = value['raw']
+ end
+
+ def start_date
+ datetime_formatter.format_date(represented.start_date, allow_nil: true)
+ end
+
+ def start_date=(value)
+ represented.start_date = datetime_formatter.parse_date(value,
+ 'startDate',
+ allow_nil: true)
+ end
+
+ def due_date
+ datetime_formatter.format_date(represented.due_date, allow_nil: true)
+ end
+
+ def due_date=(value)
+ represented.due_date = datetime_formatter.parse_date(value,
+ 'dueDate',
+ allow_nil: true)
+ end
+
+ def date=(value)
+ new_date = datetime_formatter.parse_date(value,
+ 'date',
+ allow_nil: true)
+
+ represented.due_date = represented.start_date = new_date
+ end
+
+ def date
+ datetime_formatter.format_date(represented.due_date, allow_nil: true)
+ end
def datetime_formatter
API::V3::Utilities::DateTimeFormatter
diff --git a/lib/api/v3/work_packages/work_package_relations_api.rb b/lib/api/v3/work_packages/work_package_relations_api.rb
index c2766815fcf..678982e99c0 100644
--- a/lib/api/v3/work_packages/work_package_relations_api.rb
+++ b/lib/api/v3/work_packages/work_package_relations_api.rb
@@ -51,7 +51,7 @@ module API
rep = representer.new Relation.new, current_user: current_user
relation = rep.from_json request.body.read
service = ::CreateRelationService.new user: current_user
- call = service.call relation, send_notifications: !(params[:notify] == 'false')
+ call = service.call relation, send_notifications: (params[:notify] != 'false')
if call.success?
representer.new call.result, current_user: current_user, embed_links: true
diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb
index ab46a65ea5c..83a677304d5 100644
--- a/lib/api/v3/work_packages/work_package_representer.rb
+++ b/lib/api/v3/work_packages/work_package_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -60,7 +61,7 @@ module API
super
end
- self_link title_getter: -> (*) { represented.subject }
+ self_link title_getter: ->(*) { represented.subject }
link :update do
{
@@ -278,8 +279,8 @@ module API
linked_property :parent,
path: :work_package,
- title_getter: -> (*) { represented.parent.subject },
- show_if: -> (*) { represented.parent.nil? || represented.parent.visible? }
+ title_getter: ->(*) { represented.parent.subject },
+ show_if: ->(*) { represented.parent.nil? || represented.parent.visible? }
link :timeEntries do
{
@@ -295,7 +296,7 @@ module API
linked_property :version,
getter: :fixed_version,
- title_getter: -> (*) {
+ title_getter: ->(*) {
represented.fixed_version.to_s
},
embed_as: ::API::V3::Versions::VersionRepresenter
@@ -323,42 +324,42 @@ module API
property :subject, render_nil: true
property :description,
exec_context: :decorator,
- getter: -> (*) {
+ getter: ->(*) {
::API::Decorators::Formattable.new(represented.description, object: represented)
},
- setter: -> (value, *) { represented.description = value['raw'] },
+ setter: ->(value, *) { represented.description = value['raw'] },
render_nil: true
property :start_date,
exec_context: :decorator,
- getter: -> (*) do
+ getter: ->(*) do
datetime_formatter.format_date(represented.start_date, allow_nil: true)
end,
render_nil: true,
- if: -> (_) {
+ if: ->(_) {
!represented.is_milestone?
}
property :due_date,
exec_context: :decorator,
- getter: -> (*) do
+ getter: ->(*) do
datetime_formatter.format_date(represented.due_date, allow_nil: true)
end,
render_nil: true,
- if: -> (_) {
+ if: ->(_) {
!represented.is_milestone?
}
property :date,
exec_context: :decorator,
- getter: -> (*) do
+ getter: ->(*) do
datetime_formatter.format_date(represented.due_date, allow_nil: true)
end,
render_nil: true,
- if: -> (_) {
+ if: ->(_) {
represented.is_milestone?
}
property :estimated_time,
exec_context: :decorator,
- getter: -> (*) do
+ getter: ->(*) do
datetime_formatter.format_duration_from_hours(represented.estimated_hours,
allow_nil: true)
end,
@@ -366,30 +367,29 @@ module API
writeable: false
property :spent_time,
exec_context: :decorator,
- getter: -> (*) do
+ getter: ->(*) do
datetime_formatter.format_duration_from_hours(represented.spent_hours)
end,
writeable: false,
- if: -> (_) {
+ if: ->(_) {
current_user_allowed_to(:view_time_entries, context: represented.project)
}
property :done_ratio,
as: :percentageDone,
render_nil: true,
writeable: false,
- if: -> (*) { Setting.work_package_done_ratio != 'disabled' }
- property :parent_id, writeable: true
+ if: ->(*) { Setting.work_package_done_ratio != 'disabled' }
property :created_at,
exec_context: :decorator,
- getter: -> (*) { datetime_formatter.format_datetime(represented.created_at) }
+ getter: ->(*) { datetime_formatter.format_datetime(represented.created_at) }
property :updated_at,
exec_context: :decorator,
- getter: -> (*) { datetime_formatter.format_datetime(represented.updated_at) }
+ getter: ->(*) { datetime_formatter.format_datetime(represented.updated_at) }
property :watchers,
embedded: true,
exec_context: :decorator,
- if: -> (*) {
+ if: ->(*) {
current_user_allowed_to(:view_work_package_watchers,
context: represented.project) &&
embed_links
@@ -398,12 +398,12 @@ module API
property :attachments,
embedded: true,
exec_context: :decorator,
- if: -> (*) { embed_links }
+ if: ->(*) { embed_links }
property :relations,
embedded: true,
exec_context: :decorator,
- if: -> (*) { embed_links }
+ if: ->(*) { embed_links }
def _type
'WorkPackage'
@@ -430,9 +430,9 @@ module API
def relations
self_path = api_v3_paths.work_package_relations(represented.id)
relations = represented.relations
- visible_relations = relations.select { |relation|
+ visible_relations = relations.select do |relation|
relation.other_work_package(represented).visible?
- }
+ end
::API::V3::Relations::RelationCollectionRepresenter.new(visible_relations,
self_path,
@@ -445,7 +445,7 @@ module API
self.to_eager_load = [{ children: { project: :enabled_modules } },
{ parent: { project: :enabled_modules } },
- { project: [:enabled_modules, :work_package_custom_fields] },
+ { project: %i(enabled_modules work_package_custom_fields) },
:status,
:priority,
{ type: :custom_fields },
diff --git a/lib/api/v3/work_packages/work_packages_shared_helpers.rb b/lib/api/v3/work_packages/work_packages_shared_helpers.rb
index e13f0c6f7be..677635be38f 100644
--- a/lib/api/v3/work_packages/work_packages_shared_helpers.rb
+++ b/lib/api/v3/work_packages/work_packages_shared_helpers.rb
@@ -89,7 +89,7 @@ module API
end
def notify_according_to_params
- !(params[:notify] == 'false')
+ params[:notify] != 'false'
end
end
end
diff --git a/lib/extended_http.rb b/lib/extended_http.rb
index 9c3a0c984f6..863dac84fe8 100644
--- a/lib/extended_http.rb
+++ b/lib/extended_http.rb
@@ -47,6 +47,6 @@ module ExtendedHTTP
#
# This is especially useful for successful update actions.
def no_content
- render text: '', status: :no_content
+ render html: '', status: :no_content
end
end
diff --git a/lib/open_project/patches/reform.rb b/lib/open_project/patches/reform.rb
new file mode 100644
index 00000000000..2274b8235e6
--- /dev/null
+++ b/lib/open_project/patches/reform.rb
@@ -0,0 +1,54 @@
+#-- encoding: UTF-8
+
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2017 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module OpenProject
+ module Patches
+ module Reform
+ def merge!(errors, prefix)
+ @store_new_symbols = false
+ super(errors, prefix)
+ @store_new_symbols = true
+
+ errors.keys.each do |attribute|
+ errors.symbols_and_messages_for(attribute).each do |symbol, full_message, partial_message|
+ symbols_and_messages = writable_symbols_and_messages_for(attribute)
+ next if symbols_and_messages && symbols_and_messages.any? do |sam|
+ sam[0] === symbol &&
+ sam[1] === full_message &&
+ sam[2] === partial_message
+ end
+
+ symbols_and_messages << [symbol, full_message, partial_message]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/open_project/wiki_formatting/macros/timelines_wiki_macro.rb b/lib/open_project/wiki_formatting/macros/timelines_wiki_macro.rb
index 37a29f7df86..ae4203d89e4 100644
--- a/lib/open_project/wiki_formatting/macros/timelines_wiki_macro.rb
+++ b/lib/open_project/wiki_formatting/macros/timelines_wiki_macro.rb
@@ -38,7 +38,7 @@ module OpenProject
view = options[:view]
- if view.respond_to?(:render)
+ if view.respond_to?(:javascript_include_tag)
view.render partial: '/timelines/timeline',
locals: { timeline: timeline }
else
diff --git a/lib/plugins/acts_as_journalized/lib/journal_formatter/named_association.rb b/lib/plugins/acts_as_journalized/lib/journal_formatter/named_association.rb
index bda866fbc9e..7c550969652 100644
--- a/lib/plugins/acts_as_journalized/lib/journal_formatter/named_association.rb
+++ b/lib/plugins/acts_as_journalized/lib/journal_formatter/named_association.rb
@@ -77,6 +77,10 @@ class JournalFormatter::NamedAssociation < JournalFormatter::Attribute
end
end
+ def label(key)
+ @journal.journable.class.human_attribute_name(key.to_s.gsub(/_id$/, ''))
+ end
+
def class_from_field(field)
association = @journal.journable.class.reflect_on_association(field)
diff --git a/spec/contracts/relations/create_contract_spec.rb b/spec/contracts/relations/create_contract_spec.rb
index 9b134a0de77..b6a6b909737 100644
--- a/spec/contracts/relations/create_contract_spec.rb
+++ b/spec/contracts/relations/create_contract_spec.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -30,29 +31,48 @@
require 'spec_helper'
describe Relations::CreateContract do
- let(:from) { FactoryGirl.create :work_package }
- let(:to) { FactoryGirl.create :work_package }
- let(:user) { FactoryGirl.create :admin }
+ let(:from) { FactoryGirl.build_stubbed :work_package }
+ let(:to) { FactoryGirl.build_stubbed :work_package }
+ let(:user) { FactoryGirl.build_stubbed :admin }
let(:relation) do
- Relation.new from_id: from.id, to_id: to.id, relation_type: "follows", delay: 42
+ Relation.new from: from, to: to, relation_type: "follows", delay: 42
end
subject(:contract) { described_class.new relation, user }
- describe "validating the delay" do
- class ::Delayed::DelayProxy
- def to_i
- 99
+ before do
+ allow(WorkPackage)
+ .to receive_message_chain(:visible, :exists?)
+ .and_return(true)
+ end
+
+ describe 'to' do
+ context 'not visible' do
+ before do
+ allow(WorkPackage)
+ .to receive_message_chain(:visible, :exists?)
+ .with(to.id)
+ .and_return(false)
+ end
+
+ it 'is invalid' do
+ is_expected.not_to be_valid
end
end
+ end
- it "does not trigger delayed_job and checks the correct delay" do
- begin
- expect(contract).to be_valid
- expect(contract.send(:fields).delay.to_i).to eq 42
- ensure
- ::Delayed::DelayProxy.send :remove_method, :to_i
+ describe 'from' do
+ context 'not visible' do
+ before do
+ allow(WorkPackage)
+ .to receive_message_chain(:visible, :exists?)
+ .with(from.id)
+ .and_return(false)
+ end
+
+ it 'is invalid' do
+ is_expected.not_to be_valid
end
end
end
diff --git a/spec/contracts/work_packages/base_contract_spec.rb b/spec/contracts/work_packages/base_contract_spec.rb
index 130de425e31..9fd2211f214 100644
--- a/spec/contracts/work_packages/base_contract_spec.rb
+++ b/spec/contracts/work_packages/base_contract_spec.rb
@@ -140,12 +140,8 @@ describe WorkPackages::BaseContract do
work_package.start_date = Date.today + 2.days
end
- it 'is invalid' do
- expect(contract).not_to be_valid
- end
-
it 'notes the error' do
- contract.valid?
+ contract.validate
message = I18n.t('activerecord.errors.models.work_package.attributes.start_date.violates_parent_relationships',
soonest_start: Date.today + 4.days)
diff --git a/spec/features/work_packages/select_query_spec.rb b/spec/features/work_packages/select_query_spec.rb
index f1bc1b58722..01d676eae2f 100644
--- a/spec/features/work_packages/select_query_spec.rb
+++ b/spec/features/work_packages/select_query_spec.rb
@@ -39,7 +39,7 @@ describe 'Query selection', type: :feature do
let(:filter_1_name) { 'assignee' }
let(:filter_2_name) { 'percentageDone' }
- let(:i18n_filter_1_name) { WorkPackage.human_attribute_name(:assigned_to_id) }
+ let(:i18n_filter_1_name) { WorkPackage.human_attribute_name(:assigned_to) }
let(:i18n_filter_2_name) { WorkPackage.human_attribute_name(:done_ratio) }
let(:default_status) { FactoryGirl.create(:default_status) }
diff --git a/spec/lib/api/contracts/model_contract_spec.rb b/spec/lib/api/contracts/model_contract_spec.rb
index c295003c218..fe77fb7a704 100644
--- a/spec/lib/api/contracts/model_contract_spec.rb
+++ b/spec/lib/api/contracts/model_contract_spec.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
@@ -29,7 +30,7 @@
require 'spec_helper'
describe ModelContract do
- let(:model) {
+ let(:model) do
double('The model',
child_attribute: nil,
grand_child_attribute: nil,
@@ -37,7 +38,7 @@ describe ModelContract do
changed: [],
valid?: true,
errors: ActiveModel::Errors.new(nil))
- }
+ end
let(:child_contract) { ChildContract.new(model) }
let(:grand_child_contract) { GrandChildContract.new(model) }
@@ -83,8 +84,8 @@ describe ModelContract do
'grand_child_attribute')
end
- it 'should not contain the same attribute twice' do
- expect(grand_child_contract.writable_attributes.count).to eq(3)
+ it 'should not contain the same attribute twice, but also has the _id variant' do
+ expect(grand_child_contract.writable_attributes.count).to eq(6)
end
it 'should execute all the validations' do
diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb
index 3b0cd9123a7..8c984c36a7e 100644
--- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb
+++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb
@@ -487,16 +487,6 @@ describe ::API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
end
end
- describe 'parentId' do
- it_behaves_like 'has basic schema properties' do
- let(:path) { 'parentId' }
- let(:type) { 'Integer' }
- let(:name) { I18n.t('activerecord.attributes.work_package.parent') }
- let(:required) { false }
- let(:writable) { true }
- end
- end
-
describe 'parent' do
it_behaves_like 'has basic schema properties' do
let(:path) { 'parent' }
diff --git a/spec/lib/representable_spec.rb b/spec/lib/representable_spec.rb
index ce372cbad84..772fd244d43 100644
--- a/spec/lib/representable_spec.rb
+++ b/spec/lib/representable_spec.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -64,15 +65,15 @@ describe Representable do
describe 'as_strategy with class not responding to #call?' do
it 'raises error' do
- expect {
+ expect do
class FailRepresenter < Representable::Decorator
include Representable::JSON
- self.as_strategy = Object.new
+ self.as_strategy = ::Object.new
property :title
end
- }.to raise_error(RuntimeError)
+ end.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/models/queries/work_packages/filter/version_filter_spec.rb b/spec/models/queries/work_packages/filter/version_filter_spec.rb
index 0a08834d2fc..e22892a0064 100644
--- a/spec/models/queries/work_packages/filter/version_filter_spec.rb
+++ b/spec/models/queries/work_packages/filter/version_filter_spec.rb
@@ -36,7 +36,7 @@ describe Queries::WorkPackages::Filter::VersionFilter, type: :model do
let(:type) { :list_optional }
let(:class_key) { :fixed_version_id }
let(:values) { [version.id.to_s] }
- let(:name) { WorkPackage.human_attribute_name('fixed_version_id') }
+ let(:name) { WorkPackage.human_attribute_name('fixed_version') }
before do
if project
diff --git a/spec/requests/api/v3/relations/relations_api_spec.rb b/spec/requests/api/v3/relations/relations_api_spec.rb
index 8a1a763eebe..3fc777c1b80 100644
--- a/spec/requests/api/v3/relations/relations_api_spec.rb
+++ b/spec/requests/api/v3/relations/relations_api_spec.rb
@@ -182,7 +182,7 @@ describe ::API::V3::Relations::RelationRepresenter, type: :request do
describe "permissions" do
let(:user) { FactoryGirl.create :user }
- let(:permissions) { [:view_work_packages, :manage_work_package_relations] }
+ let(:permissions) { %i(view_work_packages manage_work_package_relations) }
let(:role) do
FactoryGirl.create :existing_role, permissions: permissions
diff --git a/spec/requests/api/v3/work_package_resource_spec.rb b/spec/requests/api/v3/work_package_resource_spec.rb
index 5e62011b5e9..0e5bb36609c 100644
--- a/spec/requests/api/v3/work_package_resource_spec.rb
+++ b/spec/requests/api/v3/work_package_resource_spec.rb
@@ -307,55 +307,6 @@ describe 'API v3 Work package resource', type: :request do
end
end
- context 'parent id' do
- let(:parent) { FactoryGirl.create(:work_package, project: work_package.project) }
- let(:params) { valid_params.merge(parentId: parent.id) }
-
- before do
- allow(Setting).to receive(:cross_project_work_package_relations?).and_return(true)
- end
-
- context 'w/o permission' do
- include_context 'patch request'
-
- it { expect(response.status).to eq(403) }
- end
-
- context 'with permission' do
- before do role.add_permission!(:manage_subtasks) end
-
- include_context 'patch request'
-
- context 'invalid parent' do
- let(:params) { valid_params.merge(parentId: '-123') }
-
- it { expect(WorkPackage.visible(current_user).exists?('-123')).to be_falsey }
-
- it { expect(response.status).to eq(422) }
- end
-
- context 'empty id' do
- let(:params) { valid_params.merge(parentId: nil) }
-
- it { expect(response.status).to eq(200) }
-
- it { expect(subject.body).not_to have_json_path('parentId') }
-
- it_behaves_like 'lock version updated'
- end
-
- context 'valid id' do
- let(:params) { valid_params.merge(parentId: parent.id) }
-
- it { expect(response.status).to eq(200) }
-
- it { expect(subject.body).to be_json_eql(parent.id.to_json).at_path('parentId') }
-
- it_behaves_like 'lock version updated'
- end
- end
- end
-
context 'subject' do
let(:params) { valid_params.merge(subject: 'Updated subject') }
@@ -918,11 +869,20 @@ describe 'API v3 Work package resource', type: :request do
context 'multiple invalid attributes' do
let(:params) do
- valid_params.tap { |h| h[:subject] = '' }
- .merge(parentId: '-123')
+ valid_params
+ .tap { |h| h[:subject] = '' }
+ .merge(
+ _links: {
+ parent: {
+ href: api_v3_paths.work_package("-123")
+ }
+ }
+ )
end
- before do role.add_permission!(:manage_subtasks) end
+ before do
+ role.add_permission!(:manage_subtasks)
+ end
include_context 'patch request'
@@ -979,11 +939,11 @@ describe 'API v3 Work package resource', type: :request do
it_behaves_like 'multiple errors of the same type', 2, 'PropertyConstraintViolation'
it_behaves_like 'multiple errors of the same type with messages' do
- let(:message) {
- [child_1.id, child_2.id].map { |id|
+ let(:message) do
+ [child_1.id, child_2.id].map do |id|
"Child element ##{id}: Parent cannot be in another project."
- }
- }
+ end
+ end
end
end
end
diff --git a/spec/support/queries/filters/shared_filter_examples.rb b/spec/support/queries/filters/shared_filter_examples.rb
index b71d2ed931a..18bf1e4b491 100644
--- a/spec/support/queries/filters/shared_filter_examples.rb
+++ b/spec/support/queries/filters/shared_filter_examples.rb
@@ -37,7 +37,7 @@ shared_context 'filter tests' do
filter.values = values
filter
end
- let(:name) { model.human_attribute_name(instance_key || class_key) }
+ let(:name) { model.human_attribute_name((instance_key || class_key).to_s.gsub('_id', '')) }
let(:model) { WorkPackage }
end
diff --git a/spec/views/users/edit.html.erb_spec.rb b/spec/views/users/edit.html.erb_spec.rb
index 3c6f0795aa4..b3b346cb138 100644
--- a/spec/views/users/edit.html.erb_spec.rb
+++ b/spec/views/users/edit.html.erb_spec.rb
@@ -32,10 +32,10 @@ describe 'users/edit', type: :view do
let(:admin) { FactoryGirl.build :admin }
context 'authentication provider' do
- let(:user) {
- FactoryGirl.build :user, id: 1, # id is required to create route to edit
+ let(:user) do
+ FactoryGirl.build :user, id: 1, # id is required to create route to edit
identity_url: 'test_provider:veryuniqueid'
- }
+ end
before do
assign(:user, user)