Commit 790f8b5b authored by Jasper Maes's avatar Jasper Maes Committed by Heinrich Lee Yu
Browse files

Upgrade Rails to 5.1.6.1

Model.new.attributes now also returns encrypted attributes.
parent 232d878afbb7
# This is a monkey patch which must be removed when migrating to Rails 5.1 from 5.0.
#
# In Rails 5.0 there was introduced a bug which casts types in the uniqueness validator.
# https://github.com/rails/rails/pull/23523/commits/811a4fa8eb6ceea841e61e8ac05747ffb69595ae
#
# That causes to bugs like this:
#
# 1) API::Users POST /user/:id/gpg_keys/:key_id/revoke when authenticated revokes existing key
# Failure/Error: let(:gpg_key) { create(:gpg_key, user: user) }
#
# TypeError:
# can't cast Hash
# # ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
# # ./spec/requests/api/users_spec.rb:908:in `block (4 levels) in <top (required)>'
# # ------------------
# # --- Caused by: ---
# # TypeError:
# # TypeError
# # ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
#
# This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803
if Rails.gem_version >= Gem::Version.new("5.1")
raise "Remove this monkey patch: #{__FILE__}"
end
# Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb
# including local fixes to make Rubocop happy again.
module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
if record.persisted?
if finder_class.primary_key
relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
end
relation = scope_relation(record, table, relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
error_options = options.except(:case_sensitive, :scope, :conditions)
error_options[:value] = value
record.errors.add(attribute, :taken, error_options)
end
rescue RangeError
end
protected
def build_relation(klass, table, attribute, value) #:nodoc:
if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
# the attribute may be an aliased attribute
if klass.attribute_alias?(attribute)
attribute = klass.attribute_alias(attribute)
end
attribute_name = attribute.to_s
column = klass.columns_hash[attribute_name]
cast_type = klass.type_for_attribute(attribute_name)
comparison =
if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
if value.nil?
klass.unscoped.where(comparison)
else
bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
klass.unscoped.where(comparison, bind)
end
end
end
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# Remove once https://github.com/rails/rails/issues/32867
# is released on a 5.1.x rails version.
# Commit on 5-1-stable branch: https://github.com/rails/rails/commit/44f0df3f3980ba3aeca956839e1948b246ff34fe
module ActiveRecord
module AttributeMethods
module Dirty
def attributes_in_database
mutations_from_database.changed_values
end
end
end
module Persistence
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@mutation_tracker", @mutation_tracker ||= nil)
became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.errors.copy!(errors)
became
end
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# build_select only selects the required fields if the model has ignored_columns.
# This is incompatible with some migrations or background migration specs because
# rails keeps a statement cache in memory. So if a model with ignored_columns in a
# migration is used, the query with select table.col1, table.col2 is stored in the
# statement cache. If a different migration is then run and one of these columns is
# removed in the meantime, the query is invalid.
module ActiveRecord
module QueryMethods
private
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
else
arel.project(@klass.arel_table[Arel.star])
end
end
end
end
# rubocop:disable Lint/RescueException
# Remove this monkey patch when we move to Rails 5.1, because the bug has been fixed in https://github.com/rails/rails/pull/26050.
if Rails.gem_version >= Gem::Version.new("5.1")
raise "Remove this monkey patch: #{__FILE__}"
end
module ActiveRecord
module Locking
module Optimistic
# We overwrite this method because we don't want to have default value
# for newly created records
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
super
end
def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
previous_lock_value = send(lock_col).to_i
increment_lock
attribute_names += [lock_col]
attribute_names.uniq!
begin
relation = self.class.unscoped
affected_rows = relation.where(
self.class.primary_key => id,
# Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
lock_col => previous_lock_value == 0 ? [nil, 0] : previous_lock_value
).update_all(
attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)]
end.to_h
)
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
affected_rows
# If something went wrong, revert the version.
rescue Exception
send(lock_col + '=', previous_lock_value)
raise
end
end
# This is patched because we need it to query `lock_version IS NULL`
# rather than `lock_version = 0` whenever lock_version is NULL.
def relation_for_destroy
return super unless locking_enabled?
column_name = self.class.locking_column
super.where(self.class.arel_table[column_name].eq(self[column_name]))
end
end
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
class LockingType
def deserialize(value)
super
end
def serialize(value)
super
end
end
end
end
......@@ -4,7 +4,8 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter
NATIVE_DATABASE_TYPES.merge!(
bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' }
bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' },
serial: { name: 'int auto_increment PRIMARY KEY' }
)
end
end
......
......@@ -78,7 +78,7 @@ def add_index_options(table_name, column_name, options = {}) #:nodoc:
if index_name.length > max_index_length
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
......
......@@ -9,11 +9,11 @@ class AddUniqueIndexToSubscriptions < ActiveRecord::Migration[4.2]
def up
add_concurrent_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id, :project_id], { unique: true, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' }
remove_index :subscriptions, name: 'subscriptions_user_id_and_ref_fields' if index_name_exists?(:subscriptions, 'subscriptions_user_id_and_ref_fields', false)
remove_index :subscriptions, name: 'subscriptions_user_id_and_ref_fields' if index_name_exists?(:subscriptions, 'subscriptions_user_id_and_ref_fields')
end
def down
add_concurrent_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id], { unique: true, name: 'subscriptions_user_id_and_ref_fields' }
remove_index :subscriptions, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' if index_name_exists?(:subscriptions, 'index_subscriptions_on_subscribable_and_user_id_and_project_id', false)
remove_index :subscriptions, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' if index_name_exists?(:subscriptions, 'index_subscriptions_on_subscribable_and_user_id_and_project_id')
end
end
This diff is collapsed.
......@@ -26,11 +26,7 @@ def setup_subscribers
subscribe('sql.active_record') do |_, start, finish, _, data|
if Gitlab::SafeRequestStore.store[:peek_enabled]
# data[:cached] is only available starting from Rails 5.1.0
# https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113
# Before that, data[:name] was set to 'CACHE'
# https://github.com/rails/rails/blob/v4.2.9/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L80
unless data.fetch(:cached, data[:name] == 'CACHE')
unless data[:cached]
track_query(data[:sql].strip, data[:binds], start, finish)
end
end
......
......@@ -113,7 +113,7 @@ def issues(finder_params = {})
issues.full_search(query)
end
issues.reorder('updated_at DESC')
issues.reorder('issues.updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -121,7 +121,7 @@ def issues(finder_params = {})
def milestones
milestones = Milestone.where(project_id: project_ids_relation)
milestones = milestones.search(query)
milestones.reorder('updated_at DESC')
milestones.reorder('milestones.updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -139,7 +139,7 @@ def merge_requests
merge_requests.full_search(query)
end
merge_requests.reorder('updated_at DESC')
merge_requests.reorder('merge_requests.updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -5,7 +5,7 @@
"iid": { "type": "integer" },
"author_id": { "type": "integer" },
"description": { "type": ["string", "null"] },
"lock_version": { "type": ["string", "null"] },
"lock_version": { "type": ["integer", "null"] },
"milestone_id": { "type": ["string", "null"] },
"title": { "type": "string" },
"moved_to_id": { "type": ["integer", "null"] },
......
......@@ -23,7 +23,7 @@
},
"task_status": { "type": "string" },
"task_status_short": { "type": "string" },
"lock_version": { "type": ["string", "null"] }
"lock_version": { "type": ["integer", "null"] }
},
"additionalProperties": false
}
......@@ -58,7 +58,7 @@ describe('MergeRequest', function() {
{
merge_request: {
description: '- [ ] Task List Item',
lock_version: undefined,
lock_version: 0,
update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
},
},
......
......@@ -177,7 +177,7 @@ def convert_to_yaml(merge_request_diff_id, commits, diffs)
end
before do
allow_any_instance_of(described_class::MergeRequestDiff::ActiveRecord_Relation)
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:update_all).and_raise(exception)
end
......
......@@ -29,7 +29,7 @@
it 'has no new columns' do
relation_names.each do |relation_name|
relation_class = relation_class_for_name(relation_name)
relation_attributes = relation_class.new.attributes.keys
relation_attributes = relation_class.new.attributes.keys - relation_class.encrypted_attributes.keys.map(&:to_s)
current_attributes = parsed_attributes(relation_name, relation_attributes)
safe_attributes = safe_model_attributes[relation_class.to_s].dup || []
......
......@@ -73,9 +73,8 @@
end
it 'fails to save a group assigned to a project runner even if the runner is already saved' do
group_runner
expect { create(:group, runners: [project_runner]) }
group.runners << project_runner
expect { group.save! }
.to raise_error(ActiveRecord::RecordInvalid)
end
end
......
......@@ -68,7 +68,7 @@
post api('/runners'), params: { token: group.runners_token }
expect(response).to have_http_status 201
expect(group.runners.size).to eq(1)
expect(group.runners.reload.size).to eq(1)
runner = Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
......
......@@ -251,8 +251,8 @@
let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
context 'with the token in the query string' do
let(:get_args) { [rss_url(user), nil] }
let(:other_user_get_args) { [rss_url(other_user), nil] }
let(:get_args) { [rss_url(user), params: nil] }
let(:other_user_get_args) { [rss_url(other_user), params: nil] }
it_behaves_like 'rate-limited token-authenticated requests'
end
......
......@@ -210,6 +210,8 @@
describe "calls the before/after callbacks" do
it 'of project_members' do
expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:find).once
expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:initialize).once
expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:destroy).once
service.execute(user)
......@@ -219,6 +221,8 @@
group_member = create(:group_member)
group_member.group.group_members.create(user: user, access_level: 40)
expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:find).once
expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:initialize).once
expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:destroy).once
service.execute(user)
......
......@@ -80,7 +80,7 @@
context "when record migration fails with a rollback exception" do
before do
expect_any_instance_of(MergeRequest::ActiveRecord_Associations_CollectionProxy)
expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
.to receive(:update_all).and_raise(ActiveRecord::Rollback)
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment