activestorage_legacy 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +5 -0
  3. data/.codeclimate.yml +7 -0
  4. data/.eslintrc +19 -0
  5. data/.github/workflows/gem-push.yml +29 -0
  6. data/.github/workflows/ruby-tests.yml +37 -0
  7. data/.gitignore +9 -0
  8. data/.rubocop.yml +125 -0
  9. data/.travis.yml +25 -0
  10. data/Gemfile +33 -0
  11. data/Gemfile.lock +271 -0
  12. data/MIT-LICENSE +20 -0
  13. data/README.md +160 -0
  14. data/Rakefile +12 -0
  15. data/activestorage.gemspec +27 -0
  16. data/app/assets/javascripts/activestorage.js +1 -0
  17. data/app/controllers/active_storage/blobs_controller.rb +22 -0
  18. data/app/controllers/active_storage/direct_uploads_controller.rb +21 -0
  19. data/app/controllers/active_storage/disk_controller.rb +52 -0
  20. data/app/controllers/active_storage/variants_controller.rb +28 -0
  21. data/app/helpers/active_storage/file_field_with_direct_upload_helper.rb +18 -0
  22. data/app/javascript/activestorage/blob_record.js +54 -0
  23. data/app/javascript/activestorage/blob_upload.js +34 -0
  24. data/app/javascript/activestorage/direct_upload.js +42 -0
  25. data/app/javascript/activestorage/direct_upload_controller.js +67 -0
  26. data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
  27. data/app/javascript/activestorage/file_checksum.js +53 -0
  28. data/app/javascript/activestorage/helpers.js +42 -0
  29. data/app/javascript/activestorage/index.js +11 -0
  30. data/app/javascript/activestorage/ujs.js +74 -0
  31. data/app/jobs/active_storage/purge_attachment_worker.rb +9 -0
  32. data/app/jobs/active_storage/purge_blob_worker.rb +9 -0
  33. data/app/models/active_storage/attachment.rb +33 -0
  34. data/app/models/active_storage/blob.rb +198 -0
  35. data/app/models/active_storage/filename.rb +49 -0
  36. data/app/models/active_storage/variant.rb +82 -0
  37. data/app/models/active_storage/variation.rb +53 -0
  38. data/config/routes.rb +9 -0
  39. data/config/storage_services.yml +34 -0
  40. data/lib/active_storage/attached/macros.rb +86 -0
  41. data/lib/active_storage/attached/many.rb +51 -0
  42. data/lib/active_storage/attached/one.rb +56 -0
  43. data/lib/active_storage/attached.rb +38 -0
  44. data/lib/active_storage/engine.rb +81 -0
  45. data/lib/active_storage/gem_version.rb +15 -0
  46. data/lib/active_storage/log_subscriber.rb +48 -0
  47. data/lib/active_storage/messages_metadata.rb +64 -0
  48. data/lib/active_storage/migration.rb +27 -0
  49. data/lib/active_storage/patches/active_record.rb +19 -0
  50. data/lib/active_storage/patches/delegation.rb +98 -0
  51. data/lib/active_storage/patches/secure_random.rb +26 -0
  52. data/lib/active_storage/patches.rb +4 -0
  53. data/lib/active_storage/service/azure_service.rb +115 -0
  54. data/lib/active_storage/service/configurator.rb +28 -0
  55. data/lib/active_storage/service/disk_service.rb +124 -0
  56. data/lib/active_storage/service/gcs_service.rb +79 -0
  57. data/lib/active_storage/service/mirror_service.rb +46 -0
  58. data/lib/active_storage/service/s3_service.rb +96 -0
  59. data/lib/active_storage/service.rb +113 -0
  60. data/lib/active_storage/verifier.rb +113 -0
  61. data/lib/active_storage/version.rb +8 -0
  62. data/lib/active_storage.rb +34 -0
  63. data/lib/tasks/activestorage.rake +20 -0
  64. data/package.json +33 -0
  65. data/test/controllers/direct_uploads_controller_test.rb +123 -0
  66. data/test/controllers/disk_controller_test.rb +57 -0
  67. data/test/controllers/variants_controller_test.rb +21 -0
  68. data/test/database/create_users_migration.rb +7 -0
  69. data/test/database/setup.rb +6 -0
  70. data/test/dummy/Rakefile +3 -0
  71. data/test/dummy/app/assets/config/manifest.js +5 -0
  72. data/test/dummy/app/assets/images/.keep +0 -0
  73. data/test/dummy/app/assets/javascripts/application.js +13 -0
  74. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  75. data/test/dummy/app/controllers/application_controller.rb +3 -0
  76. data/test/dummy/app/controllers/concerns/.keep +0 -0
  77. data/test/dummy/app/helpers/application_helper.rb +2 -0
  78. data/test/dummy/app/jobs/application_job.rb +2 -0
  79. data/test/dummy/app/models/application_record.rb +3 -0
  80. data/test/dummy/app/models/concerns/.keep +0 -0
  81. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  82. data/test/dummy/bin/bundle +3 -0
  83. data/test/dummy/bin/rails +4 -0
  84. data/test/dummy/bin/rake +4 -0
  85. data/test/dummy/bin/yarn +11 -0
  86. data/test/dummy/config/application.rb +22 -0
  87. data/test/dummy/config/boot.rb +5 -0
  88. data/test/dummy/config/database.yml +25 -0
  89. data/test/dummy/config/environment.rb +5 -0
  90. data/test/dummy/config/environments/development.rb +49 -0
  91. data/test/dummy/config/environments/production.rb +82 -0
  92. data/test/dummy/config/environments/test.rb +33 -0
  93. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  94. data/test/dummy/config/initializers/assets.rb +14 -0
  95. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  96. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  97. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  98. data/test/dummy/config/initializers/inflections.rb +16 -0
  99. data/test/dummy/config/initializers/mime_types.rb +4 -0
  100. data/test/dummy/config/initializers/secret_key.rb +3 -0
  101. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  102. data/test/dummy/config/routes.rb +2 -0
  103. data/test/dummy/config/secrets.yml +32 -0
  104. data/test/dummy/config/spring.rb +6 -0
  105. data/test/dummy/config/storage_services.yml +3 -0
  106. data/test/dummy/config.ru +5 -0
  107. data/test/dummy/db/.keep +0 -0
  108. data/test/dummy/lib/assets/.keep +0 -0
  109. data/test/dummy/log/.keep +0 -0
  110. data/test/dummy/package.json +5 -0
  111. data/test/dummy/public/404.html +67 -0
  112. data/test/dummy/public/422.html +67 -0
  113. data/test/dummy/public/500.html +66 -0
  114. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  115. data/test/dummy/public/apple-touch-icon.png +0 -0
  116. data/test/dummy/public/favicon.ico +0 -0
  117. data/test/filename_test.rb +36 -0
  118. data/test/fixtures/files/racecar.jpg +0 -0
  119. data/test/models/attachments_test.rb +122 -0
  120. data/test/models/blob_test.rb +47 -0
  121. data/test/models/variant_test.rb +27 -0
  122. data/test/service/.gitignore +1 -0
  123. data/test/service/azure_service_test.rb +14 -0
  124. data/test/service/configurations-example.yml +31 -0
  125. data/test/service/configurator_test.rb +14 -0
  126. data/test/service/disk_service_test.rb +12 -0
  127. data/test/service/gcs_service_test.rb +42 -0
  128. data/test/service/mirror_service_test.rb +62 -0
  129. data/test/service/s3_service_test.rb +52 -0
  130. data/test/service/shared_service_tests.rb +66 -0
  131. data/test/sidekiq/minitest_support.rb +6 -0
  132. data/test/support/assertions.rb +20 -0
  133. data/test/test_helper.rb +69 -0
  134. data/webpack.config.js +27 -0
  135. data/yarn.lock +3164 -0
  136. metadata +330 -0
@@ -0,0 +1,86 @@
1
+ # Provides the class-level DSL for declaring that an Active Record model has attached blobs.
2
+ module ActiveStorage::Attached::Macros
3
+ # Specifies the relation between a single attachment and the model.
4
+ #
5
+ # class User < ActiveRecord::Base
6
+ # has_one_attached :avatar
7
+ # end
8
+ #
9
+ # There is no column defined on the model side, Active Storage takes
10
+ # care of the mapping between your records and the attachment.
11
+ #
12
+ # Under the covers, this relationship is implemented as a `has_one` association to a
13
+ # `ActiveStorage::Attachment` record and a `has_one-through` association to a
14
+ # `ActiveStorage::Blob` record. These associations are available as `avatar_attachment`
15
+ # and `avatar_blob`. But you shouldn't need to work with these associations directly in
16
+ # most circumstances.
17
+ #
18
+ # The system has been designed to having you go through the `ActiveStorage::Attached::One`
19
+ # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`.
20
+ #
21
+ # If the +:dependent+ option isn't set, the attachment will be purged
22
+ # (i.e. destroyed) whenever the record is destroyed.
23
+ def has_one_attached(name, dependent: :purge_later)
24
+ define_method(name) do
25
+ instance_variable_get("@active_storage_attached_#{name}") ||
26
+ instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self))
27
+ end
28
+
29
+ if Rails.version < '4.0'
30
+ has_one :"#{name}_attachment", conditions: proc { "name = '#{name}'" }, class_name: "ActiveStorage::Attachment", as: :record
31
+ else
32
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record
33
+ end
34
+
35
+ has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
36
+
37
+ if dependent == :purge_later
38
+ before_destroy { public_send(name).purge_later }
39
+ end
40
+ end
41
+
42
+ # Specifies the relation between multiple attachments and the model.
43
+ #
44
+ # class Gallery < ActiveRecord::Base
45
+ # has_many_attached :photos
46
+ # end
47
+ #
48
+ # There are no columns defined on the model side, Active Storage takes
49
+ # care of the mapping between your records and the attachments.
50
+ #
51
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
52
+ #
53
+ # Gallery.where(user: Current.user).with_attached_photos
54
+ #
55
+ # Under the covers, this relationship is implemented as a `has_many` association to a
56
+ # `ActiveStorage::Attachment` record and a `has_many-through` association to a
57
+ # `ActiveStorage::Blob` record. These associations are available as `photos_attachments`
58
+ # and `photos_blobs`. But you shouldn't need to work with these associations directly in
59
+ # most circumstances.
60
+ #
61
+ # The system has been designed to having you go through the `ActiveStorage::Attached::Many`
62
+ # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`.
63
+ #
64
+ # If the +:dependent+ option isn't set, all the attachments will be purged
65
+ # (i.e. destroyed) whenever the record is destroyed.
66
+ def has_many_attached(name, dependent: :purge_later)
67
+ define_method(name) do
68
+ instance_variable_get("@active_storage_attached_#{name}") ||
69
+ instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::Many.new(name, self))
70
+ end
71
+
72
+ if Rails.version < '4.0'
73
+ has_many :"#{name}_attachments", conditions: proc { "name = '#{name}'" }, as: :record, class_name: "ActiveStorage::Attachment"
74
+ else
75
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment"
76
+ end
77
+
78
+ has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
79
+
80
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
81
+
82
+ if dependent == :purge_later
83
+ before_destroy { public_send(name).purge_later }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,51 @@
1
+ # Decorated proxy object representing of multiple attachments to a model.
2
+ class ActiveStorage::Attached::Many < ActiveStorage::Attached
3
+ delegate_missing_to :attachments
4
+
5
+ # Returns all the associated attachment records.
6
+ #
7
+ # All methods called on this proxy object that aren't listed here will automatically be delegated to `attachments`.
8
+ def attachments
9
+ record.public_send("#{name}_attachments")
10
+ end
11
+
12
+ # Associates one or several attachments with the current record, saving them to the database.
13
+ # Examples:
14
+ #
15
+ # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
16
+ # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
17
+ # document.images.attach(io: File.open("~/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
18
+ # document.images.attach([ first_blob, second_blob ])
19
+ def attach(*attachables)
20
+ attachables.flatten.collect do |attachable|
21
+ attachments.create!(name: name, blob: create_blob_from(attachable))
22
+ end
23
+ end
24
+
25
+ # Returns true if any attachments has been made.
26
+ #
27
+ # class Gallery < ActiveRecord::Base
28
+ # has_many_attached :photos
29
+ # end
30
+ #
31
+ # Gallery.new.photos.attached? # => false
32
+ def attached?
33
+ attachments.any?
34
+ end
35
+
36
+ # Directly purges each associated attachment (i.e. destroys the blobs and
37
+ # attachments and deletes the files on the service).
38
+ def purge
39
+ if attached?
40
+ attachments.each(&:purge)
41
+ attachments.reload
42
+ end
43
+ end
44
+
45
+ # Purges each associated attachment through the queuing system.
46
+ def purge_later
47
+ if attached?
48
+ attachments.each(&:purge_later)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ # Representation of a single attachment to a model.
2
+ class ActiveStorage::Attached::One < ActiveStorage::Attached
3
+ delegate_missing_to :attachment
4
+
5
+ # Returns the associated attachment record.
6
+ #
7
+ # You don't have to call this method to access the attachment's methods as
8
+ # they are all available at the model level.
9
+ def attachment
10
+ record.public_send("#{name}_attachment")
11
+ end
12
+
13
+ # Associates a given attachment with the current record, saving it to the database.
14
+ # Examples:
15
+ #
16
+ # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
17
+ # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
18
+ # person.avatar.attach(io: File.open("~/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
19
+ # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
20
+ def attach(attachable)
21
+ write_attachment \
22
+ ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable))
23
+ end
24
+
25
+ # Returns true if an attachment has been made.
26
+ #
27
+ # class User < ActiveRecord::Base
28
+ # has_one_attached :avatar
29
+ # end
30
+ #
31
+ # User.new.avatar.attached? # => false
32
+ def attached?
33
+ attachment.present?
34
+ end
35
+
36
+ # Directly purges the attachment (i.e. destroys the blob and
37
+ # attachment and deletes the file on the service).
38
+ def purge
39
+ if attached?
40
+ attachment.purge
41
+ write_attachment nil
42
+ end
43
+ end
44
+
45
+ # Purges the attachment through the queuing system.
46
+ def purge_later
47
+ if attached?
48
+ attachment.purge_later
49
+ end
50
+ end
51
+
52
+ private
53
+ def write_attachment(attachment)
54
+ record.public_send("#{name}_attachment=", attachment)
55
+ end
56
+ end
@@ -0,0 +1,38 @@
1
+ require "active_storage/blob"
2
+ require "active_storage/attachment"
3
+
4
+ require "action_dispatch/http/upload"
5
+ require "active_storage/patches/delegation"
6
+
7
+ # Abstract baseclass for the concrete `ActiveStorage::Attached::One` and `ActiveStorage::Attached::Many`
8
+ # classes that both provide proxy access to the blob association for a record.
9
+ class ActiveStorage::Attached
10
+ attr_reader :name, :record
11
+
12
+ def initialize(name, record)
13
+ @name, @record = name, record
14
+ end
15
+
16
+ private
17
+ def create_blob_from(attachable)
18
+ case attachable
19
+ when ActiveStorage::Blob
20
+ attachable
21
+ when ActionDispatch::Http::UploadedFile
22
+ ActiveStorage::Blob.create_after_upload! \
23
+ io: attachable.open,
24
+ filename: attachable.original_filename,
25
+ content_type: attachable.content_type
26
+ when Hash
27
+ ActiveStorage::Blob.create_after_upload!(attachable)
28
+ when String
29
+ ActiveStorage::Blob.find_signed(attachable)
30
+ else
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ require "active_storage/attached/one"
37
+ require "active_storage/attached/many"
38
+ require "active_storage/attached/macros"
@@ -0,0 +1,81 @@
1
+ require "rails/engine"
2
+
3
+ module ActiveStorage
4
+ class Engine < Rails::Engine # :nodoc:
5
+ config.active_storage = ActiveSupport::OrderedOptions.new
6
+
7
+ # config.eager_load_namespaces << ActiveStorage
8
+
9
+ initializer "active_storage.logger" do
10
+ require "active_storage/service"
11
+
12
+ config.after_initialize do |app|
13
+ ActiveStorage::Service.logger = app.config.active_storage.logger || Rails.logger
14
+ end
15
+ end
16
+
17
+ initializer 'active_storage.extend_active_record' do
18
+ require "active_storage/patches"
19
+ require "active_storage/patches/active_record"
20
+
21
+ ActiveSupport.on_load :active_record do
22
+ extend ActiveStorage::Patches::ActiveRecord
23
+ end
24
+ end
25
+
26
+ initializer "active_storage.attached" do
27
+ require "active_storage/attached"
28
+
29
+ ActiveSupport.on_load(:active_record) do
30
+ extend ActiveStorage::Attached::Macros
31
+ end
32
+ end
33
+
34
+ # Port of Rails.application.key_generator.generate_key('ActiveStorage')
35
+ # Used to sign ActiveStorage::Blob
36
+ # See: https://github.com/rails/activestorage/blob/archive/lib/active_storage/engine.rb#L27
37
+ # https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/key_generator.rb#L40
38
+ initializer "active_storage.verifier" do
39
+ require 'active_storage/verifier'
40
+
41
+ config.after_initialize do |app|
42
+ key = OpenSSL::PKCS5.pbkdf2_hmac(
43
+ app.config.secret_token,
44
+ 'ActiveStorage',
45
+ 1000,
46
+ 64,
47
+ OpenSSL::Digest::SHA256.new
48
+ )
49
+ ActiveStorage.verifier = ActiveStorage::Verifier.new(key)
50
+ end
51
+ end
52
+
53
+ initializer "active_storage.services" do
54
+ config.after_initialize do |app|
55
+ if config_choice = app.config.active_storage.service
56
+ config_file = Pathname.new(Rails.root.join("config/storage_services.yml"))
57
+ raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
58
+
59
+ require "yaml"
60
+ require "erb"
61
+
62
+ configs =
63
+ begin
64
+ YAML.load(ERB.new(config_file.read).result) || {}
65
+ rescue Psych::SyntaxError => e
66
+ raise "YAML syntax error occurred while parsing #{config_file}. " \
67
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
68
+ "Error: #{e.message}"
69
+ end
70
+
71
+ ActiveStorage::Blob.service =
72
+ begin
73
+ ActiveStorage::Service.configure config_choice, configs
74
+ rescue => e
75
+ raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveStorage
2
+ # Returns the version of the currently loaded Active Storage as a <tt>Gem::Version</tt>
3
+ def self.gem_version
4
+ Gem::Version.new VERSION::STRING
5
+ end
6
+
7
+ module VERSION
8
+ MAJOR = 0
9
+ MINOR = 1
10
+ TINY = 0
11
+ PRE = "alpha"
12
+
13
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ require "active_support/log_subscriber"
2
+
3
+ class ActiveStorage::LogSubscriber < ActiveSupport::LogSubscriber
4
+ def service_upload(event)
5
+ message = "Uploaded file to key: #{key_in(event)}"
6
+ message << " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
7
+ info event, color(message, GREEN)
8
+ end
9
+
10
+ def service_download(event)
11
+ info event, color("Downloaded file from key: #{key_in(event)}", BLUE)
12
+ end
13
+
14
+ def service_delete(event)
15
+ info event, color("Deleted file from key: #{key_in(event)}", RED)
16
+ end
17
+
18
+ def service_exist(event)
19
+ debug event, color("Checked if file exist at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE)
20
+ end
21
+
22
+ def service_url(event)
23
+ debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE)
24
+ end
25
+
26
+ def logger
27
+ ActiveStorage::Service.logger
28
+ end
29
+
30
+ private
31
+ def info(event, colored_message)
32
+ super log_prefix_for_service(event) + colored_message
33
+ end
34
+
35
+ def debug(event, colored_message)
36
+ super log_prefix_for_service(event) + colored_message
37
+ end
38
+
39
+ def log_prefix_for_service(event)
40
+ color " #{event.payload[:service]} Storage (#{event.duration.round(1)}ms) ", CYAN
41
+ end
42
+
43
+ def key_in(event)
44
+ event.payload[:key]
45
+ end
46
+ end
47
+
48
+ ActiveStorage::LogSubscriber.attach_to :active_storage
@@ -0,0 +1,64 @@
1
+ class ActiveSupport::MessagesMetadata #:nodoc:
2
+ def initialize(message, expires_at = nil, purpose = nil)
3
+ @message, @expires_at, @purpose = message, expires_at, purpose
4
+ end
5
+
6
+ def as_json(options = {})
7
+ { _rails: { message: @message, exp: @expires_at, pur: @purpose } }
8
+ end
9
+
10
+ class << self
11
+ def wrap(message, expires_at: nil, expires_in: nil, purpose: nil)
12
+ if expires_at || expires_in || purpose
13
+ ActiveSupport::JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
14
+ else
15
+ message
16
+ end
17
+ end
18
+
19
+ def verify(message, purpose)
20
+ extract_metadata(message).verify(purpose)
21
+ end
22
+
23
+ private
24
+ def pick_expiry(expires_at, expires_in)
25
+ if expires_at
26
+ expires_at.utc.iso8601(3)
27
+ elsif expires_in
28
+ Time.now.utc.advance(seconds: expires_in).iso8601(3)
29
+ end
30
+ end
31
+
32
+ def extract_metadata(message)
33
+ data = ActiveSupport::JSON.decode(message) rescue nil
34
+
35
+ if data.is_a?(Hash) && data.key?("_rails")
36
+ new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"])
37
+ else
38
+ new(message)
39
+ end
40
+ end
41
+
42
+ def encode(message)
43
+ ::Base64.strict_encode64(message)
44
+ end
45
+
46
+ def decode(message)
47
+ ::Base64.strict_decode64(message)
48
+ end
49
+ end
50
+
51
+ def verify(purpose)
52
+ @message if match?(purpose) && fresh?
53
+ end
54
+
55
+ private
56
+ def match?(purpose)
57
+ @purpose.to_s == purpose.to_s
58
+ end
59
+
60
+ def fresh?
61
+ @expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at)
62
+ end
63
+ end
64
+
@@ -0,0 +1,27 @@
1
+ class ActiveStorageCreateTables < ActiveRecord::Migration
2
+ def change
3
+ create_table :active_storage_blobs do |t|
4
+ t.string :key
5
+ t.string :filename
6
+ t.string :content_type
7
+ t.text :metadata
8
+ t.integer :byte_size
9
+ t.string :checksum
10
+ t.datetime :created_at
11
+ end
12
+
13
+ add_index :active_storage_blobs, :key, unique: true
14
+
15
+ create_table :active_storage_attachments do |t|
16
+ t.string :name
17
+ t.string :record_type
18
+ t.integer :record_id
19
+ t.integer :blob_id
20
+
21
+ t.datetime :created_at
22
+ end
23
+
24
+ add_index :active_storage_attachments, :blob_id
25
+ add_index :active_storage_attachments, [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # Provides the class-level DSL for declaring that an Active Record model has attached blobs.
2
+
3
+ module ActiveStorage::Patches::ActiveRecord
4
+ class MinimumLengthError < StandardError; end
5
+ MINIMUM_TOKEN_LENGTH = 24
6
+
7
+ def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH)
8
+ if length < MINIMUM_TOKEN_LENGTH
9
+ raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
10
+ end
11
+
12
+ define_method("regenerate_#{attribute}") { update_attributes! attribute => self.class.generate_unique_secure_token(length: length) }
13
+ before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
14
+ end
15
+
16
+ def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
17
+ SecureRandom.base58(length)
18
+ end
19
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ class Module
6
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
+ # option is not used.
8
+ class DelegationError < NoMethodError; end
9
+
10
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
11
+ case class def defined? do else elsif END end ensure false for if in module next nil
12
+ not or redo rescue retry return self super then true undef unless until when while yield)
13
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
14
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
15
+ RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
16
+ ).freeze
17
+
18
+ # When building decorators, a common pattern may emerge:
19
+ #
20
+ # class Partition
21
+ # def initialize(event)
22
+ # @event = event
23
+ # end
24
+ #
25
+ # def person
26
+ # detail.person || creator
27
+ # end
28
+ #
29
+ # private
30
+ # def respond_to_missing?(name, include_private = false)
31
+ # @event.respond_to?(name, include_private)
32
+ # end
33
+ #
34
+ # def method_missing(method, *args, &block)
35
+ # @event.send(method, *args, &block)
36
+ # end
37
+ # end
38
+ #
39
+ # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
40
+ #
41
+ # class Partition
42
+ # delegate_missing_to :@event
43
+ #
44
+ # def initialize(event)
45
+ # @event = event
46
+ # end
47
+ #
48
+ # def person
49
+ # detail.person || creator
50
+ # end
51
+ # end
52
+ #
53
+ # The target can be anything callable within the object, e.g. instance
54
+ # variables, methods, constants, etc.
55
+ #
56
+ # The delegated method must be public on the target, otherwise it will
57
+ # raise +DelegationError+. If you wish to instead return +nil+,
58
+ # use the <tt>:allow_nil</tt> option.
59
+ #
60
+ # The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
61
+ # delegation due to possible interference when calling
62
+ # <tt>Marshal.dump(object)</tt>, should the delegation target method
63
+ # of <tt>object</tt> add or remove instance variables.
64
+ def delegate_missing_to(target, allow_nil: nil)
65
+ target = target.to_s
66
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
67
+
68
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
69
+ def respond_to_missing?(name, include_private = false)
70
+ # It may look like an oversight, but we deliberately do not pass
71
+ # +include_private+, because they do not get delegated.
72
+
73
+ return false if name == :marshal_dump || name == :_dump
74
+ #{target}.respond_to?(name) || super
75
+ end
76
+
77
+ def method_missing(method, *args, &block)
78
+ if #{target}.respond_to?(method)
79
+ #{target}.public_send(method, *args, &block)
80
+ else
81
+ begin
82
+ super
83
+ rescue NoMethodError
84
+ if #{target}.nil?
85
+ if #{allow_nil == true}
86
+ nil
87
+ else
88
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
89
+ end
90
+ else
91
+ raise
92
+ end
93
+ end
94
+ end
95
+ end
96
+ RUBY
97
+ end
98
+ end
@@ -0,0 +1,26 @@
1
+ require "securerandom"
2
+
3
+ module SecureRandom
4
+ BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"] unless defined?(BASE58_ALPHABET)
5
+ BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a unless defined?(BASE36_ALPHABET)
6
+
7
+ unless SecureRandom.methods.include?(:base58)
8
+ def self.base58(n = 16)
9
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
10
+ idx = byte % 64
11
+ idx = SecureRandom.random_number(58) if idx >= 58
12
+ BASE58_ALPHABET[idx]
13
+ end.join
14
+ end
15
+ end
16
+
17
+ unless SecureRandom.methods.include?(:base36)
18
+ def self.base36(n = 16)
19
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
20
+ idx = byte % 64
21
+ idx = SecureRandom.random_number(36) if idx >= 36
22
+ BASE36_ALPHABET[idx]
23
+ end.join
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveStorage::Patches
2
+ end
3
+
4
+ require "active_storage/patches/secure_random"