2019-04-30 08:27:42 +08:00
# frozen_string_literal: true
2014-04-28 16:31:51 +08:00
describe HasCustomFields do
2014-04-25 20:10:54 +08:00
context " custom_fields " do
before do
2018-06-19 14:13:14 +08:00
DB . exec ( " create temporary table custom_fields_test_items(id SERIAL primary key) " )
2019-07-07 02:42:03 +08:00
DB . exec ( " create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text, created_at TIMESTAMP, updated_at TIMESTAMP) " )
2019-07-05 01:25:09 +08:00
DB . exec ( << ~ SQL )
CREATE UNIQUE INDEX ON custom_fields_test_item_custom_fields ( custom_fields_test_item_id )
WHERE NAME = 'rare'
SQL
2014-04-25 20:10:54 +08:00
2014-04-28 16:37:34 +08:00
class CustomFieldsTestItem < ActiveRecord :: Base
2014-04-28 16:31:51 +08:00
include HasCustomFields
2014-04-25 20:10:54 +08:00
end
2014-04-28 16:37:34 +08:00
class CustomFieldsTestItemCustomField < ActiveRecord :: Base
belongs_to :custom_fields_test_item
2014-04-25 20:10:54 +08:00
end
end
after do
2018-06-19 14:13:14 +08:00
DB . exec ( " drop table custom_fields_test_items " )
DB . exec ( " drop table custom_fields_test_item_custom_fields " )
2014-04-25 20:10:54 +08:00
2019-04-30 14:58:18 +08:00
# this weakref in the descendant tracker should clean up the two tests
# if this becomes an issue we can revisit (watch out for erratic tests)
Object . send ( :remove_const , :CustomFieldsTestItem )
Object . send ( :remove_const , :CustomFieldsTestItemCustomField )
2014-04-25 20:10:54 +08:00
end
2022-01-21 12:29:51 +08:00
it " allows preloading of custom fields " do
test_item = CustomFieldsTestItem . new
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " test_field " ] )
expect ( test_item . preloaded_custom_fields ) . to eq ( { " test_field " = > nil } )
end
it " errors if a custom field is not preloaded " do
test_item = CustomFieldsTestItem . new
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " test_field " ] )
expect { test_item . custom_fields [ " other_field " ] } . to raise_error ( HasCustomFields :: NotPreloadedError )
end
it " resets the preloaded_custom_fields if preload_custom_fields is called twice " do
test_item = CustomFieldsTestItem . new
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " test_field " ] )
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " other_field " ] )
expect ( test_item . preloaded_custom_fields ) . to eq ( { " other_field " = > nil } )
end
it " does not error with NotPreloadedError if preload_custom_fields is called twice " do
test_item = CustomFieldsTestItem . new
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " test_field " ] )
expect { test_item . custom_fields [ " test_field " ] } . not_to raise_error
CustomFieldsTestItem . preload_custom_fields ( [ test_item ] , [ " other_field " ] )
expect { test_item . custom_fields [ " other_field " ] } . not_to raise_error
end
2019-07-05 01:25:09 +08:00
it " allows simple modification of custom fields " do
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( nil )
2014-04-25 20:10:54 +08:00
test_item . custom_fields [ " bob " ] = " marley "
test_item . custom_fields [ " jack " ] = " black "
2014-04-26 01:15:23 +08:00
2014-04-25 20:10:54 +08:00
test_item . save
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . find ( test_item . id )
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " bob " ] ) . to eq ( " marley " )
expect ( test_item . custom_fields [ " jack " ] ) . to eq ( " black " )
2014-04-25 20:10:54 +08:00
test_item . custom_fields . delete ( " bob " )
test_item . custom_fields [ " jack " ] = " jill "
test_item . save
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . find ( test_item . id )
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields ) . to eq ( " jack " = > " jill " )
2014-04-25 20:10:54 +08:00
end
2014-04-26 01:15:23 +08:00
it " casts integers to string without error " do
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( nil )
2014-04-26 01:15:23 +08:00
test_item . custom_fields [ " a " ] = 0
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( 0 )
2014-04-26 01:15:23 +08:00
test_item . save
# should be casted right after saving
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( " 0 " )
2014-04-26 01:15:23 +08:00
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . find ( test_item . id )
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( " 0 " )
2014-04-26 01:15:23 +08:00
end
2019-07-05 01:25:09 +08:00
it " reloads from the database " do
2014-04-30 01:23:13 +08:00
test_item = CustomFieldsTestItem . new
test_item . custom_fields [ " a " ] = 0
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( 0 )
2014-04-30 01:23:13 +08:00
test_item . save
# should be casted right after saving
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( " 0 " )
2014-04-30 01:23:13 +08:00
2018-06-19 14:13:14 +08:00
DB . exec ( " UPDATE custom_fields_test_item_custom_fields SET value='1' WHERE custom_fields_test_item_id=? AND name='a' " , test_item . id )
2014-04-30 01:23:13 +08:00
# still the same, did not load
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( " 0 " )
2014-04-30 01:23:13 +08:00
# refresh loads from database
2015-01-10 00:34:37 +08:00
expect ( test_item . reload . custom_fields [ " a " ] ) . to eq ( " 1 " )
expect ( test_item . custom_fields [ " a " ] ) . to eq ( " 1 " )
2014-04-30 01:23:13 +08:00
end
2014-04-26 00:22:49 +08:00
2019-07-05 01:25:09 +08:00
it " actually saves on double save " do
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2014-04-26 00:22:49 +08:00
test_item . custom_fields = { " a " = > " b " }
test_item . save
test_item . custom_fields [ " c " ] = " d "
test_item . save
2014-04-28 16:37:34 +08:00
db_item = CustomFieldsTestItem . find ( test_item . id )
2015-01-10 00:34:37 +08:00
expect ( db_item . custom_fields ) . to eq ( " a " = > " b " , " c " = > " d " )
2014-04-26 00:22:49 +08:00
end
2014-04-26 01:15:23 +08:00
it " handles arrays properly " do
2018-05-22 14:48:39 +08:00
CustomFieldsTestItem . register_custom_field_type " array " , [ :integer ]
test_item = CustomFieldsTestItem . new
test_item . custom_fields = { " array " = > [ " 1 " ] }
test_item . save
db_item = CustomFieldsTestItem . find ( test_item . id )
expect ( db_item . custom_fields ) . to eq ( " array " = > [ 1 ] )
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2014-04-26 01:15:23 +08:00
test_item . custom_fields = { " a " = > [ " b " , " c " , " d " ] }
test_item . save
2014-04-28 16:37:34 +08:00
db_item = CustomFieldsTestItem . find ( test_item . id )
2015-01-10 00:34:37 +08:00
expect ( db_item . custom_fields ) . to eq ( " a " = > [ " b " , " c " , " d " ] )
2014-04-26 01:15:23 +08:00
2015-01-03 04:56:44 +08:00
db_item . custom_fields . update ( 'a' = > [ 'c' , 'd' ] )
2014-04-26 01:15:23 +08:00
db_item . save
2015-01-10 00:34:37 +08:00
expect ( db_item . custom_fields ) . to eq ( " a " = > [ " c " , " d " ] )
2014-04-26 01:15:23 +08:00
2015-01-16 04:31:31 +08:00
# It can be updated to the exact same value
db_item . custom_fields . update ( 'a' = > [ 'c' ] )
db_item . save
expect ( db_item . custom_fields ) . to eq ( " a " = > " c " )
db_item . custom_fields . update ( 'a' = > [ 'c' ] )
db_item . save
expect ( db_item . custom_fields ) . to eq ( " a " = > " c " )
2015-01-03 04:56:44 +08:00
db_item . custom_fields . delete ( 'a' )
2015-01-10 00:34:37 +08:00
expect ( db_item . custom_fields ) . to eq ( { } )
2014-04-26 01:15:23 +08:00
end
2021-06-25 17:34:51 +08:00
it " deletes nil-filled arrays " do
test_item = CustomFieldsTestItem . create!
db_item = CustomFieldsTestItem . find ( test_item . id )
db_item . custom_fields . update ( " a " = > [ nil , nil ] )
db_item . save_custom_fields
db_item . custom_fields . delete ( " a " )
expect ( db_item . custom_fields ) . to eq ( { } )
db_item . save_custom_fields
expect ( db_item . custom_fields ) . to eq ( { } )
end
2014-04-26 01:15:23 +08:00
it " casts integers in arrays properly without error " do
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2014-04-26 01:15:23 +08:00
test_item . custom_fields = { " a " = > [ " b " , 10 , " d " ] }
test_item . save
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields ) . to eq ( " a " = > [ " b " , " 10 " , " d " ] )
2014-04-26 01:15:23 +08:00
2014-04-28 16:37:34 +08:00
db_item = CustomFieldsTestItem . find ( test_item . id )
2015-01-10 00:34:37 +08:00
expect ( db_item . custom_fields ) . to eq ( " a " = > [ " b " , " 10 " , " d " ] )
2014-04-26 01:15:23 +08:00
end
2021-05-21 09:43:47 +08:00
it " supports type coercion " do
2014-06-17 10:42:12 +08:00
test_item = CustomFieldsTestItem . new
CustomFieldsTestItem . register_custom_field_type ( " bool " , :boolean )
CustomFieldsTestItem . register_custom_field_type ( " int " , :integer )
2015-04-24 01:33:29 +08:00
CustomFieldsTestItem . register_custom_field_type ( " json " , :json )
2014-06-17 10:42:12 +08:00
2015-04-24 01:33:29 +08:00
test_item . custom_fields = { " bool " = > true , " int " = > 1 , " json " = > { " foo " = > " bar " } }
2014-06-17 10:42:12 +08:00
test_item . save
test_item . reload
2015-04-24 01:33:29 +08:00
expect ( test_item . custom_fields ) . to eq ( " bool " = > true , " int " = > 1 , " json " = > { " foo " = > " bar " } )
2017-08-17 05:04:40 +08:00
before_ids = CustomFieldsTestItemCustomField . where ( custom_fields_test_item_id : test_item . id ) . pluck ( :id )
test_item . custom_fields [ " bool " ] = false
test_item . save
after_ids = CustomFieldsTestItemCustomField . where ( custom_fields_test_item_id : test_item . id ) . pluck ( :id )
# we updated only 1 custom field, so there should be only 1 different id
expect ( ( before_ids - after_ids ) . size ) . to eq ( 1 )
2014-06-17 10:42:12 +08:00
end
2019-07-05 01:25:09 +08:00
it " doesn't allow simple modifications to interfere " do
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . new
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields [ " a " ] ) . to eq ( nil )
2014-04-25 20:10:54 +08:00
test_item . custom_fields [ " bob " ] = " marley "
test_item . custom_fields [ " jack " ] = " black "
test_item . save
2014-04-28 16:37:34 +08:00
test_item2 = CustomFieldsTestItem . new
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item2 . custom_fields [ " x " ] ) . to eq ( nil )
2014-04-25 20:10:54 +08:00
test_item2 . custom_fields [ " sixto " ] = " rodriguez "
test_item2 . custom_fields [ " de " ] = " la playa "
test_item2 . save
2014-04-28 16:37:34 +08:00
test_item = CustomFieldsTestItem . find ( test_item . id )
test_item2 = CustomFieldsTestItem . find ( test_item2 . id )
2014-04-25 20:10:54 +08:00
2015-01-10 00:34:37 +08:00
expect ( test_item . custom_fields ) . to eq ( " jack " = > " black " , " bob " = > " marley " )
expect ( test_item2 . custom_fields ) . to eq ( " sixto " = > " rodriguez " , " de " = > " la playa " )
2014-04-25 20:10:54 +08:00
end
2014-05-15 02:38:04 +08:00
2018-09-13 15:59:17 +08:00
it " supports arrays in json fields " do
field_type = " json_array "
CustomFieldsTestItem . register_custom_field_type ( field_type , :json )
item = CustomFieldsTestItem . new
item . custom_fields = {
" json_array " = > [ { a : " test " } , { b : " another " } ]
}
item . save
item . reload
expect ( item . custom_fields [ field_type ] ) . to eq (
[ { " a " = > " test " } , { " b " = > " another " } ]
)
item . custom_fields [ " json_array " ] = [ 'a' , 'b' ]
item . save
item . reload
expect ( item . custom_fields [ field_type ] ) . to eq ( [ " a " , " b " ] )
end
it " will not fail to load custom fields if json is corrupt " do
field_type = " bad_json "
CustomFieldsTestItem . register_custom_field_type ( field_type , :json )
item = CustomFieldsTestItem . create!
CustomFieldsTestItemCustomField . create! (
custom_fields_test_item_id : item . id ,
name : field_type ,
value : " {test "
)
item = item . reload
expect ( item . custom_fields [ field_type ] ) . to eq ( { } )
end
2014-05-15 02:38:04 +08:00
it " supports bulk retrieval with a list of ids " do
item1 = CustomFieldsTestItem . new
2020-07-27 08:23:54 +08:00
item1 . custom_fields = { " a " = > [ " b " , " c " , " d " ] , 'not_allowlisted' = > 'secret' }
2014-05-15 02:38:04 +08:00
item1 . save
item2 = CustomFieldsTestItem . new
item2 . custom_fields = { " e " = > 'hallo' }
item2 . save
fields = CustomFieldsTestItem . custom_fields_for_ids ( [ item1 . id , item2 . id ] , [ 'a' , 'e' ] )
2015-01-10 00:34:37 +08:00
expect ( fields ) . to be_present
expect ( fields [ item1 . id ] [ 'a' ] ) . to match_array ( [ 'b' , 'c' , 'd' ] )
2020-07-27 08:23:54 +08:00
expect ( fields [ item1 . id ] [ 'not_allowlisted' ] ) . to be_blank
2015-01-10 00:34:37 +08:00
expect ( fields [ item2 . id ] [ 'e' ] ) . to eq ( 'hallo' )
2014-05-15 02:38:04 +08:00
end
2017-11-08 11:10:20 +08:00
it " handles interleaving saving properly " do
field_type = 'deep-nest-test'
CustomFieldsTestItem . register_custom_field_type ( field_type , :json )
test_item = CustomFieldsTestItem . create!
test_item . custom_fields [ field_type ] || = { }
test_item . custom_fields [ field_type ] [ 'b' ] || = { }
test_item . custom_fields [ field_type ] [ 'b' ] [ 'c' ] = 'd'
test_item . save_custom_fields ( true )
db_item = CustomFieldsTestItem . find ( test_item . id )
db_item . custom_fields [ field_type ] [ 'b' ] [ 'e' ] = 'f'
test_item . custom_fields [ field_type ] [ 'b' ] [ 'e' ] = 'f'
expected = { field_type = > { 'b' = > { 'c' = > 'd' , 'e' = > 'f' } } }
db_item . save_custom_fields ( true )
expect ( db_item . reload . custom_fields ) . to eq ( expected )
test_item . save_custom_fields ( true )
expect ( test_item . reload . custom_fields ) . to eq ( expected )
end
2018-03-03 01:45:34 +08:00
2022-04-26 00:19:39 +08:00
it 'determines clean state correctly for mutable fields' do
json_field = " json_field "
array_field = " array_field "
CustomFieldsTestItem . register_custom_field_type ( json_field , :json )
CustomFieldsTestItem . register_custom_field_type ( array_field , :array )
item_with_array = CustomFieldsTestItem . new
expect ( item_with_array . custom_fields_clean? ) . to eq ( true )
item_with_array . custom_fields [ array_field ] = [ 1 ]
expect ( item_with_array . custom_fields_clean? ) . to eq ( false )
item_with_array . save!
expect ( item_with_array . custom_fields_clean? ) . to eq ( true )
item_with_array . custom_fields [ array_field ] << 2
expect ( item_with_array . custom_fields_clean? ) . to eq ( false )
item_with_array . save!
expect ( item_with_array . custom_fields_clean? ) . to eq ( true )
item_with_json = CustomFieldsTestItem . new
expect ( item_with_json . custom_fields_clean? ) . to eq ( true )
item_with_json . custom_fields [ json_field ] = { " hello " = > " world " }
expect ( item_with_json . custom_fields_clean? ) . to eq ( false )
item_with_json . save!
expect ( item_with_json . custom_fields_clean? ) . to eq ( true )
item_with_json . custom_fields [ json_field ] [ " hello " ] = " world2 "
expect ( item_with_json . custom_fields_clean? ) . to eq ( false )
item_with_json . save!
expect ( item_with_json . custom_fields_clean? ) . to eq ( true )
end
2019-07-05 01:25:09 +08:00
describe " create_singular " do
it " creates new records " do
item = CustomFieldsTestItem . create!
item . create_singular ( 'hello' , 'world' )
expect ( item . reload . custom_fields [ 'hello' ] ) . to eq ( 'world' )
end
it " upserts on a database constraint error " do
item0 = CustomFieldsTestItem . new
item0 . custom_fields = { " rare " = > " gem " }
item0 . save
2019-07-07 02:42:03 +08:00
expect ( item0 . reload . custom_fields [ 'rare' ] ) . to eq ( " gem " )
2019-07-05 01:25:09 +08:00
item0 . create_singular ( 'rare' , " diamond " )
expect ( item0 . reload . custom_fields [ 'rare' ] ) . to eq ( " diamond " )
end
end
2018-03-03 01:45:34 +08:00
describe " upsert_custom_fields " do
it 'upserts records' do
test_item = CustomFieldsTestItem . create
test_item . upsert_custom_fields ( 'hello' = > 'world' , 'abc' = > 'def' )
# In memory
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'def' )
# Persisted
test_item . reload
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'def' )
# In memory
test_item . upsert_custom_fields ( 'abc' = > 'ghi' )
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'ghi' )
# Persisted
test_item . reload
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'ghi' )
end
2019-10-30 02:34:28 +08:00
it 'allows upsert to use keywords' do
test_item = CustomFieldsTestItem . create
test_item . upsert_custom_fields ( hello : 'world' , abc : 'def' )
# In memory
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'def' )
# Persisted
test_item . reload
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'def' )
# In memory
test_item . upsert_custom_fields ( 'abc' = > 'ghi' )
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'ghi' )
# Persisted
test_item . reload
expect ( test_item . custom_fields [ 'hello' ] ) . to eq ( 'world' )
expect ( test_item . custom_fields [ 'abc' ] ) . to eq ( 'ghi' )
end
2020-08-25 14:09:34 +08:00
2020-08-25 18:28:41 +08:00
it 'allows using string and symbol indices interchangeably' do
2020-08-25 14:09:34 +08:00
test_item = CustomFieldsTestItem . new
test_item . custom_fields [ " bob " ] = " marley "
test_item . custom_fields [ " jack " ] = " black "
2022-01-21 12:29:51 +08:00
# In memory
expect ( test_item . custom_fields [ :bob ] ) . to eq ( 'marley' )
expect ( test_item . custom_fields [ :jack ] ) . to eq ( 'black' )
2020-08-25 14:09:34 +08:00
2022-01-21 12:29:51 +08:00
# Persisted
test_item . save
test_item . reload
expect ( test_item . custom_fields [ :bob ] ) . to eq ( 'marley' )
expect ( test_item . custom_fields [ :jack ] ) . to eq ( 'black' )
2020-08-25 14:09:34 +08:00
2022-01-21 12:29:51 +08:00
# Update via string index again
test_item . custom_fields [ 'bob' ] = 'the builder'
2020-08-25 14:09:34 +08:00
2022-01-21 12:29:51 +08:00
expect ( test_item . custom_fields [ :bob ] ) . to eq ( 'the builder' )
test_item . save
test_item . reload
2020-08-25 14:09:34 +08:00
2022-01-21 12:29:51 +08:00
expect ( test_item . custom_fields [ :bob ] ) . to eq ( 'the builder' )
2020-08-25 14:09:34 +08:00
end
2018-03-03 01:45:34 +08:00
end
2014-04-25 20:10:54 +08:00
end
end