attachy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +108 -0
  5. data/lib/assets/javascripts/attachy.js +289 -0
  6. data/lib/attachy.rb +13 -0
  7. data/lib/attachy/builders/attachy/form_builder.rb +15 -0
  8. data/lib/attachy/engine.rb +16 -0
  9. data/lib/attachy/helpers/attachy/view_helper.rb +31 -0
  10. data/lib/attachy/models/attachy/extension.rb +79 -0
  11. data/lib/attachy/models/attachy/file.rb +59 -0
  12. data/lib/attachy/models/attachy/viewer.rb +170 -0
  13. data/lib/attachy/version.rb +3 -0
  14. data/lib/generators/attachy/install_generator.rb +25 -0
  15. data/lib/generators/attachy/templates/config/attachy.yml.erb +15 -0
  16. data/lib/generators/attachy/templates/db/migrate/create_attachy_files_table.rb +19 -0
  17. data/lib/generators/attachy/templates/public/cloudinary_cors.html +46 -0
  18. data/spec/builders/attachy/form_builder/attachy_content_spec.rb +35 -0
  19. data/spec/builders/attachy/form_builder/attachy_file_field_spec.rb +23 -0
  20. data/spec/builders/attachy/form_builder/attachy_spec.rb +35 -0
  21. data/spec/factories/attachy/file.rb +12 -0
  22. data/spec/factories/user.rb +5 -0
  23. data/spec/helpers/attachy/attachy_content_spec.rb +21 -0
  24. data/spec/helpers/attachy/attachy_file_field_spec.rb +21 -0
  25. data/spec/helpers/attachy/attachy_spec.rb +35 -0
  26. data/spec/models/attachy/callback/destroy_file_spec.rb +55 -0
  27. data/spec/models/attachy/callback/remove_tmp_tag_spec.rb +11 -0
  28. data/spec/models/attachy/extension/user/avatar_spec.rb +91 -0
  29. data/spec/models/attachy/extension/user/photos_spec.rb +88 -0
  30. data/spec/models/attachy/file/config_spec.rb +11 -0
  31. data/spec/models/attachy/file/default_spec.rb +26 -0
  32. data/spec/models/attachy/file/path_spec.rb +16 -0
  33. data/spec/models/attachy/file/transform_spec.rb +86 -0
  34. data/spec/models/attachy/file_spec.rb +14 -0
  35. data/spec/models/attachy/viewer/attachments_spec.rb +28 -0
  36. data/spec/models/attachy/viewer/button_label_options_spec.rb +15 -0
  37. data/spec/models/attachy/viewer/button_label_spec.rb +34 -0
  38. data/spec/models/attachy/viewer/content_options_spec.rb +29 -0
  39. data/spec/models/attachy/viewer/content_spec.rb +62 -0
  40. data/spec/models/attachy/viewer/field_options_spec.rb +18 -0
  41. data/spec/models/attachy/viewer/field_spec.rb +56 -0
  42. data/spec/models/attachy/viewer/file_button_options_spec.rb +18 -0
  43. data/spec/models/attachy/viewer/file_button_spec.rb +57 -0
  44. data/spec/models/attachy/viewer/file_field_options_spec.rb +90 -0
  45. data/spec/models/attachy/viewer/file_field_spec.rb +25 -0
  46. data/spec/models/attachy/viewer/hidden_field_spec.rb +22 -0
  47. data/spec/models/attachy/viewer/image_spec.rb +170 -0
  48. data/spec/models/attachy/viewer/link_options_spec.rb +18 -0
  49. data/spec/models/attachy/viewer/link_spec.rb +131 -0
  50. data/spec/models/attachy/viewer/node_options_spec.rb +18 -0
  51. data/spec/models/attachy/viewer/node_spec.rb +134 -0
  52. data/spec/models/attachy/viewer/nodes_spec.rb +21 -0
  53. data/spec/models/attachy/viewer/remove_button_options_spec.rb +18 -0
  54. data/spec/models/attachy/viewer/transform_spec.rb +44 -0
  55. data/spec/models/attachy/viewer/value_spec.rb +83 -0
  56. data/spec/models/user_spec.rb +9 -0
  57. data/spec/rails_helper.rb +11 -0
  58. data/spec/support/common.rb +20 -0
  59. data/spec/support/database_cleaner.rb +19 -0
  60. data/spec/support/db/migrate/create_users_table.rb +7 -0
  61. data/spec/support/factory_girl.rb +7 -0
  62. data/spec/support/html_matchers.rb +5 -0
  63. data/spec/support/migrate.rb +4 -0
  64. data/spec/support/models/user.rb +5 -0
  65. data/spec/support/shoulda.rb +8 -0
  66. metadata +365 -0
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE HTML>
2
+
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ </head>
7
+ <body>
8
+ <script>
9
+ /*
10
+ json2.js
11
+ 2016-10-28
12
+
13
+ Public Domain.
14
+
15
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
16
+
17
+ See http://www.JSON.org/js.html
18
+ This code should be minified before deployment.
19
+
20
+ See http://javascript.crockford.com/jsmin.html
21
+
22
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
23
+ NOT CONTROL.
24
+ */
25
+
26
+ "object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return a<10?"0"+a:a}function this_value(){return this.valueOf()}function quote(a){return rx_escapable.lastIndex=0,rx_escapable.test(a)?'"'+a.replace(rx_escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,h,g=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,h=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;c<f;c+=1)h[c]=str(c,i)||"null";return e=0===h.length?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;c<f;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));return e=0===h.length?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g,e}}var rx_one=/^[\],:{}\s]*$/,rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,rx_four=/(?:^|:|,)(?:\s*\[)+/g,rx_escapable=/[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,rx_dangerous=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},Boolean.prototype.toJSON=this_value,Number.prototype.toJSON=this_value,String.prototype.toJSON=this_value);var gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;d<c;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),rx_dangerous.lastIndex=0,rx_dangerous.test(text)&&(text=text.replace(rx_dangerous,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),rx_one.test(text.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
27
+ /* end of json2.js */
28
+
29
+ function parse(query) {
30
+ var
31
+ result = {},
32
+ params = query.split('&');
33
+
34
+ for (var i = 0; i < params.length; i++) {
35
+ var param = params[i].split('=');
36
+
37
+ result[param[0]] = decodeURIComponent(param[1]);
38
+ }
39
+
40
+ return JSON.stringify(result);
41
+ }
42
+
43
+ document.body.textContent = document.body.innerText = parse(window.location.search.slice(1));
44
+ </script>
45
+ </body>
46
+ </html>
@@ -0,0 +1,35 @@
1
+ require 'rails_helper'
2
+
3
+ class Dummy < ActionView::Helpers::FormBuilder
4
+ include Attachy::FormBuilder
5
+ end
6
+
7
+ class DummyHelper
8
+ include Attachy::ViewHelper
9
+ end
10
+
11
+ RSpec.describe Dummy, '.attachy_content' do
12
+ let!(:method) { :avatar }
13
+ let!(:options) { { key: :value } }
14
+ let!(:object) { create :user }
15
+ let!(:template) { DummyHelper.new }
16
+ let!(:dummy) { described_class.new method, object, template, options }
17
+
18
+ context 'with no block' do
19
+ xit 'delegates to view helper' do
20
+ expect(template).to receive(:attachy_content).with(method, object, options, nil)
21
+
22
+ dummy.attachy_content method, options
23
+ end
24
+ end
25
+
26
+ context 'with block' do
27
+ let!(:block) { proc {} }
28
+
29
+ xit 'delegates to view helper with block' do
30
+ expect(template).to receive(:attachy_content).with(method, object, options, block)
31
+
32
+ dummy.attachy_content method, options, &block
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails_helper'
2
+
3
+ class Dummy < ActionView::Helpers::FormBuilder
4
+ include Attachy::FormBuilder
5
+ end
6
+
7
+ class DummyHelper
8
+ include Attachy::ViewHelper
9
+ end
10
+
11
+ RSpec.describe Dummy, '.attachy_file_field' do
12
+ let!(:method) { :avatar }
13
+ let!(:options) { { key: :value } }
14
+ let!(:object) { create :user }
15
+ let!(:template) { DummyHelper.new }
16
+ let!(:dummy) { described_class.new method, object, template, options }
17
+
18
+ it 'delegates to view helper' do
19
+ expect(template).to receive(:attachy_file_field).with(method, object, options)
20
+
21
+ dummy.attachy_file_field method, options
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ require 'rails_helper'
2
+
3
+ class Dummy < ActionView::Helpers::FormBuilder
4
+ include Attachy::FormBuilder
5
+ end
6
+
7
+ class DummyHelper
8
+ include Attachy::ViewHelper
9
+ end
10
+
11
+ RSpec.describe Dummy, '.attachy' do
12
+ let!(:method) { :avatar }
13
+ let!(:options) { { key: :value } }
14
+ let!(:object) { create :user }
15
+ let!(:template) { DummyHelper.new }
16
+ let!(:dummy) { described_class.new method, object, template, options }
17
+
18
+ context 'with no block' do
19
+ it 'delegates to view helper' do
20
+ expect(template).to receive(:attachy).with(method, object, options, nil)
21
+
22
+ dummy.attachy method, options
23
+ end
24
+ end
25
+
26
+ context 'with block' do
27
+ let!(:block) { proc {} }
28
+
29
+ it 'delegates to view helper with block' do
30
+ expect(template).to receive(:attachy).with(method, object, options, block)
31
+
32
+ dummy.attachy method, options, &block
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ FactoryGirl.define do
2
+ factory :file, class: Attachy::File do
3
+ format :jpg
4
+ height 600
5
+ scope :avatar
6
+ width 800
7
+
8
+ sequence(:public_id) { |i| "PublicId#{i}" }
9
+
10
+ sequence :version, &:to_s
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :user do
3
+ sequence(:name) { |i| "Name #{i}" }
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ class DummyHelper
4
+ include Attachy::ViewHelper
5
+ end
6
+
7
+ RSpec.describe DummyHelper, '.attachy_content' do
8
+ let!(:method) { :avatar }
9
+ let!(:options) { { key: :value } }
10
+ let!(:object) { create :user }
11
+ let!(:helper) { DummyHelper.new }
12
+ let!(:viewer) { double Attachy::Viewer, content: :content }
13
+
14
+ before do
15
+ allow(Attachy::Viewer).to receive(:new).with(method, object, options, helper) { viewer }
16
+ end
17
+
18
+ it 'calls content from viewer' do
19
+ expect(helper.attachy_content(method, object, options)).to eq :content
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ class DummyHelper
4
+ include Attachy::ViewHelper
5
+ end
6
+
7
+ RSpec.describe DummyHelper, '.attachy_file_field' do
8
+ let!(:method) { :avatar }
9
+ let!(:options) { { key: :value } }
10
+ let!(:object) { create :user }
11
+ let!(:helper) { DummyHelper.new }
12
+ let!(:viewer) { double Attachy::Viewer, file_field: :file_field }
13
+
14
+ before do
15
+ allow(Attachy::Viewer).to receive(:new).with(method, object, options, helper) { viewer }
16
+ end
17
+
18
+ it 'calls file_field from viewer' do
19
+ expect(helper.attachy_file_field(method, object, options)).to eq :file_field
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ require 'rails_helper'
2
+
3
+ class DummyHelper
4
+ include Attachy::ViewHelper
5
+ end
6
+
7
+ RSpec.describe DummyHelper, '.attachy' do
8
+ let!(:method) { :avatar }
9
+ let!(:options) { { key: :value } }
10
+ let!(:object) { create :user }
11
+ let!(:helper) { DummyHelper.new }
12
+ let!(:viewer) { double Attachy::Viewer, field: :field }
13
+
14
+ context 'with no block' do
15
+ before do
16
+ allow(Attachy::Viewer).to receive(:new).with(method, object, options, helper) { viewer }
17
+ end
18
+
19
+ it 'calls field from viewer' do
20
+ expect(helper.attachy(method, object, options, nil)).to eq :field
21
+ end
22
+ end
23
+
24
+ context 'with block' do
25
+ let!(:block) { proc { |v| expect(v.field).to eq :field } }
26
+
27
+ before do
28
+ allow(Attachy::Viewer).to receive(:new).with(method, object, options, helper) { viewer }
29
+ end
30
+
31
+ it 'delegates to view helper with block' do
32
+ helper.attachy method, object, options, block
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,55 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe User, ':destroy_file' do
4
+ let!(:user) { create :user }
5
+
6
+ let!(:photo) do
7
+ expect(Cloudinary::Uploader).to receive(:remove_tag)
8
+
9
+ create :file, public_id: 'public_id', scope: :photos, attachable: user
10
+ end
11
+
12
+ context 'via assign' do
13
+ context 'via assign on relation' do
14
+ it 'removes the file via api' do
15
+ expect(Cloudinary::Uploader).to receive(:destroy).with('public_id')
16
+
17
+ user.photos_files = []
18
+ end
19
+ end
20
+
21
+ context 'via assign on scope' do
22
+ it 'removes the file via api' do
23
+ expect(Cloudinary::Uploader).to receive(:destroy).with('public_id')
24
+
25
+ user.photos = '[]'
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'via method' do
31
+ context 'via method over one record' do
32
+ it 'removes the file via api' do
33
+ expect(Cloudinary::Uploader).to receive(:destroy).with('public_id')
34
+
35
+ user.photos_files.first.destroy
36
+ end
37
+ end
38
+
39
+ context 'via method over criteria' do
40
+ it 'removes the file via api' do
41
+ expect(Cloudinary::Uploader).to receive(:destroy).with('public_id')
42
+
43
+ user.photos_files.destroy_all
44
+ end
45
+ end
46
+
47
+ context 'via .clear' do
48
+ it 'does not removes the file via api' do
49
+ expect(Cloudinary::Uploader).not_to receive(:destroy)
50
+
51
+ user.photos_files.clear
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe User, ':remove_tmp_tag' do
4
+ let!(:user) { create :user }
5
+
6
+ it 'removes the tmp tag via api' do
7
+ expect(Cloudinary::Uploader).to receive(:remove_tag).with(Attachy::TMP_TAG, ['public_id'])
8
+
9
+ create :file, public_id: 'public_id', attachable: user
10
+ end
11
+ end
@@ -0,0 +1,91 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe User, ':avatar' do
4
+ before do
5
+ allow(Cloudinary::Uploader).to receive(:remove_tag)
6
+ allow(Cloudinary::Uploader).to receive(:destroy)
7
+ end
8
+
9
+ describe ':avatar_files' do
10
+ let!(:user) { create :user }
11
+ let!(:avatar_1) { create :file, scope: :avatar, attachable: user }
12
+ let!(:avatar_2) { create :file, scope: :avatar, attachable: user }
13
+
14
+ it 'returns all records even in the singular' do
15
+ expect(user.avatar_files).to match_array [avatar_1, avatar_2]
16
+ end
17
+ end
18
+
19
+ describe ':avatar_files=' do
20
+ let!(:user) { create :user }
21
+ let!(:avatar_1) { create :file, scope: :avatar }
22
+ let!(:avatar_2) { create :file, scope: :avatar }
23
+
24
+ context 'when given records' do
25
+ before { user.avatar_files = [avatar_1, avatar_2] }
26
+
27
+ it 'is saved' do
28
+ expect(user.avatar_files).to match_array [avatar_1, avatar_2]
29
+ end
30
+ end
31
+
32
+ context 'when given no records' do
33
+ before { user.avatar_files = [avatar_1, avatar_2] }
34
+
35
+ it 'clears the existents' do
36
+ user.avatar_files = []
37
+
38
+ expect(user.avatar_files).to eq []
39
+ end
40
+ end
41
+ end
42
+
43
+ describe ':avatar' do
44
+ let!(:user) { create :user }
45
+
46
+ context 'with no file' do
47
+ before do
48
+ allow(Attachy::File).to receive(:default) { :default }
49
+ end
50
+
51
+ it 'returns a default file' do
52
+ expect(user.avatar).to eq :default
53
+ end
54
+ end
55
+
56
+ context 'with file' do
57
+ let!(:avatar_1) { create :file, scope: :avatar, attachable: user }
58
+ let!(:avatar_2) { create :file, scope: :avatar, attachable: user }
59
+
60
+ it 'returns just the last one simulating a has_one' do
61
+ expect(user.avatar).to eq avatar_2
62
+ end
63
+ end
64
+ end
65
+
66
+ describe ':avatar?' do
67
+ let!(:user) { create :user }
68
+
69
+ context 'with no records' do
70
+ specify { expect(user.avatar?).to eq false }
71
+ end
72
+
73
+ context 'with records' do
74
+ before { create :file, scope: :avatar, attachable: user }
75
+
76
+ specify { expect(user.avatar?).to eq true }
77
+ end
78
+ end
79
+
80
+ describe ':avatar_metadata' do
81
+ let!(:user) { create :user }
82
+
83
+ it 'returns the metadata' do
84
+ expect(user.avatar_metadata).to eq(
85
+ accept: %i[jpg png],
86
+ multiple: false,
87
+ scope: :avatar
88
+ )
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,88 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe User, ':photos' do
4
+ before do
5
+ allow(Cloudinary::Uploader).to receive(:remove_tag)
6
+ allow(Cloudinary::Uploader).to receive(:destroy)
7
+ end
8
+
9
+ describe ':photos_files' do
10
+ let!(:user) { create :user }
11
+ let!(:photo_1) { create :file, scope: :photos, attachable: user }
12
+ let!(:photo_2) { create :file, scope: :photos, attachable: user }
13
+
14
+ it 'returns all records' do
15
+ expect(user.photos_files).to match_array [photo_1, photo_2]
16
+ end
17
+ end
18
+
19
+ describe ':photos_files=' do
20
+ let!(:user) { create :user }
21
+ let!(:photo_1) { create :file, scope: :photo }
22
+ let!(:photo_2) { create :file, scope: :photo }
23
+
24
+ context 'when given records' do
25
+ before { user.photos_files = [photo_1, photo_2] }
26
+
27
+ it 'is saved' do
28
+ expect(user.photos_files).to match_array [photo_1, photo_2]
29
+ end
30
+ end
31
+
32
+ context 'when given no records' do
33
+ before { user.photos_files = [photo_1, photo_2] }
34
+
35
+ it 'clears the existents' do
36
+ user.photos_files = []
37
+
38
+ expect(user.photos_files).to eq []
39
+ end
40
+ end
41
+ end
42
+
43
+ describe ':photo' do
44
+ let!(:user) { create :user }
45
+
46
+ context 'with no file' do
47
+ it 'returns empty' do
48
+ expect(user.photos).to eq []
49
+ end
50
+ end
51
+
52
+ context 'with file' do
53
+ let!(:photo_1) { create :file, scope: :photos, attachable: user }
54
+ let!(:photo_2) { create :file, scope: :photos, attachable: user }
55
+
56
+ it 'returns all records' do
57
+ expect(user.photos).to match_array [photo_1, photo_2]
58
+ end
59
+ end
60
+ end
61
+
62
+ describe ':photo?' do
63
+ let!(:user) { create :user }
64
+
65
+ context 'with no records' do
66
+ specify { expect(user.photos?).to eq false }
67
+ end
68
+
69
+ context 'with records' do
70
+ before { create :file, scope: :photos, attachable: user }
71
+
72
+ specify { expect(user.photos?).to eq true }
73
+ end
74
+ end
75
+
76
+ describe ':photos_metadata' do
77
+ let!(:user) { create :user }
78
+
79
+ it 'returns the metadata' do
80
+ expect(user.photos_metadata).to eq(
81
+ accept: %i[jpg png],
82
+ maximum: 10,
83
+ multiple: true,
84
+ scope: :photos
85
+ )
86
+ end
87
+ end
88
+ end