active_storage-postgresql 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 02204d5bc6e8a8e41d30c3d38ae5308345dce145c9b80f98127dacd8c74ac3ba
4
+ data.tar.gz: 2e15d75e150d8d3be1634f21f7c75b6018fa6a4b75f079aa301de2a526ff3f43
5
+ SHA512:
6
+ metadata.gz: 0f94e2258f91ef67cba241bca7936225f9f11076b307114a81df7b6f9b5d20aa7d126dd136c94d31813e045395635f3ad6de36b5f8eaf638d4b1c91d319db28a
7
+ data.tar.gz: c244fed09f914b4ce819577949ae12a33f80c8d4f0cee9987d3efd0952eb8a9048a52ba5aaaec959e1ef1b51d1045585313fb0ca63a1827d6684605a1853b5fe
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Lachlan Sylvester
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ # ActiveStorage::PostgreSQL
2
+
3
+ ActiveStorage Service to store files PostgeSQL.
4
+
5
+ Files are stored in PostgreSQL as Large Objects, which provide streaming style access.
6
+ More information about Large Objects can be found [here](https://www.postgresql.org/docs/current/static/largeobjects.html).
7
+
8
+ This allows use of ActiveStorage on hosting platforms with ephemeral file systems such as Heroku without relying on third party storage services.
9
+
10
+ There are [some limits](https://dba.stackexchange.com/questions/127270/what-are-the-limits-of-postgresqls-large-object-facility) to the storage of Large Objects in PostgerSQL, so this is only recommended for prototyping and very small sites.
11
+
12
+ ## Installation
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'active_storage-postgresql', git: "https://github.com/lsylvester/active_storage-postgresql"
17
+ ```
18
+
19
+ And then execute:
20
+ ```bash
21
+ $ bundle
22
+ ```
23
+
24
+ In config/storage.yml set PostgreSQL as the service
25
+
26
+ ```yaml
27
+ local:
28
+ service: PostgreSQL
29
+ ```
30
+
31
+ Copy over the migrations:
32
+
33
+ ```
34
+ rails active_storage:install
35
+ rails active_storage:postgresql:install
36
+ ```
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at https://github.com/lsylvester/active_storage-postgresql. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
41
+
42
+ ## Acknowledgments
43
+
44
+ Special thanks to [diogob](https://github.com/diogob) whos work on [carrierwave-postgresql](https://github.com/diogob/carrierwave-postgresql) inspired this.
45
+
46
+ ## License
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
48
+
49
+ ## Code of Conduct
50
+
51
+ Everyone interacting in the ActiveStorage::PostgreSQL project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/lsylvester/active_storage-postgresql/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Activestorage::PostgreSQL'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
@@ -0,0 +1,10 @@
1
+ class CreateActiveStoragePostgresqlTables < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :active_storage_postgresql_files do |t|
4
+ t.oid :oid
5
+ t.string :key
6
+
7
+ t.index :key, unique: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ require "active_storage/postgresql/engine"
2
+
3
+ module ActiveStorage
4
+ module PostgreSQL
5
+ # Your code goes here...
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveStorage
2
+ module PostgreSQL
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace ActiveStorage::PostgreSQL
5
+
6
+ railtie_name 'active_storage_postgresql'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ class ActiveStorage::PostgreSQL::File < ActiveRecord::Base
2
+ self.table_name = "active_storage_postgresql_files"
3
+
4
+ before_create do
5
+ self.oid ||= lo_creat
6
+ end
7
+
8
+ def self.open(key, &block)
9
+ find_by!(key: key).open(&block)
10
+ end
11
+
12
+ def open(*args)
13
+ transaction do
14
+ begin
15
+ @lo = lo_open(oid, *args)
16
+ yield(self)
17
+ ensure
18
+ lo_close(@lo) if @lo
19
+ end
20
+ end
21
+ end
22
+
23
+ def write(content)
24
+ lo_write(@lo, content)
25
+ end
26
+
27
+ def read(bytes=size)
28
+ lo_read(@lo, bytes)
29
+ end
30
+
31
+ def seek(position)
32
+ lo_seek(@lo, position, 0)
33
+ end
34
+
35
+ def import(path)
36
+ transaction do
37
+ self.oid = lo_import(path)
38
+ end
39
+ end
40
+
41
+ def size
42
+ current_position = lo_tell(@lo)
43
+ lo_seek(@lo, 0,2)
44
+ lo_tell(@lo).tap do
45
+ lo_seek(@lo, current_position,0)
46
+ end
47
+ end
48
+
49
+ before_destroy do
50
+ self.class.connection.raw_connection.lo_unlink(oid)
51
+ end
52
+
53
+ delegate :lo_seek, :lo_tell, :lo_import, :lo_read, :lo_write, :lo_open,
54
+ :lo_unlink, :lo_close, :lo_creat, to: 'self.class.connection.raw_connection'
55
+
56
+ scope :prefixed_with, -> prefix { where("key like ?", "#{prefix}%") }
57
+
58
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveStorage
2
+ module PostgreSQL
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'active_storage/service/postgresql_service'
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_storage/postgresql/file'
4
+
5
+ module ActiveStorage
6
+ # Wraps a PostgreSQL database as an Active Storage service. See ActiveStorage::Service for the generic API
7
+ # documentation that applies to all services.
8
+ class Service::PostgreSQLService < Service
9
+ def initialize(**options)
10
+ end
11
+
12
+ def upload(key, io, checksum: nil)
13
+ instrument :upload, key: key, checksum: checksum do
14
+ if io.respond_to?(:to_path)
15
+ ActiveStorage::PostgreSQL::File.create!(key: key) do |file|
16
+ file.import(io.to_path)
17
+ end
18
+ if checksum
19
+ unless Digest::MD5.file(io.to_path).base64digest == checksum
20
+ delete key
21
+ raise ActiveStorage::IntegrityError
22
+ end
23
+ end
24
+ else
25
+ md5 = Digest::MD5.new
26
+ ActiveStorage::PostgreSQL::File.create!(key: key).open(::PG::INV_WRITE) do |file|
27
+ while data = io.read(5.megabytes)
28
+ md5.update(data)
29
+ file.write(data)
30
+ end
31
+ end
32
+ if checksum
33
+ unless md5.base64digest == checksum
34
+ delete key
35
+ raise ActiveStorage::IntegrityError
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def download(key)
43
+ if block_given?
44
+ instrument :streaming_download, key: key do
45
+ ActiveStorage::PostgreSQL::File.open(key) do |file|
46
+ while data = file.read(5.megabytes)
47
+ yield data
48
+ end
49
+ end
50
+ end
51
+ else
52
+ instrument :download, key: key do
53
+ ActiveStorage::PostgreSQL::File.open(key) do |file|
54
+ file.read
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def download_chunk(key, range)
61
+ instrument :download_chunk, key: key, range: range do
62
+ ActiveStorage::PostgreSQL::File.open(key) do |file|
63
+ file.seek(range.first)
64
+ file.read(range.size)
65
+ end
66
+ end
67
+ end
68
+
69
+ def delete(key)
70
+ instrument :delete, key: key do
71
+ ActiveStorage::PostgreSQL::File.find_by(key: key).try(&:destroy)
72
+ end
73
+ end
74
+
75
+ def exist?(key)
76
+ instrument :exist, key: key do |payload|
77
+ answer = ActiveStorage::PostgreSQL::File.where(key: key).exists?
78
+ payload[:exist] = answer
79
+ answer
80
+ end
81
+ end
82
+
83
+ def delete_prefixed(prefix)
84
+ instrument :delete_prefixed, prefix: prefix do
85
+ ActiveStorage::PostgreSQL::File.prefixed_with(prefix).destroy_all
86
+ end
87
+ end
88
+
89
+ def url(key, expires_in:, filename:, disposition:, content_type:)
90
+ instrument :url, key: key do |payload|
91
+ verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key)
92
+
93
+ generated_url =
94
+ url_helpers.rails_disk_service_url(
95
+ verified_key_with_expiration,
96
+ host: current_host,
97
+ filename: filename,
98
+ disposition: content_disposition_with(type: disposition, filename: filename),
99
+ content_type: content_type
100
+ )
101
+
102
+ payload[:url] = generated_url
103
+
104
+ generated_url
105
+ end
106
+ end
107
+
108
+ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
109
+ instrument :url, key: key do |payload|
110
+ verified_token_with_expiration = ActiveStorage.verifier.generate(
111
+ {
112
+ key: key,
113
+ content_type: content_type,
114
+ content_length: content_length,
115
+ checksum: checksum
116
+ },
117
+ { expires_in: expires_in,
118
+ purpose: :blob_token }
119
+ )
120
+
121
+ generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host)
122
+
123
+ payload[:url] = generated_url
124
+
125
+ generated_url
126
+ end
127
+ end
128
+
129
+ def headers_for_direct_upload(key, content_type:, **)
130
+ { "Content-Type" => content_type }
131
+ end
132
+
133
+ protected
134
+
135
+ def url_helpers
136
+ @url_helpers ||= Rails.application.routes.url_helpers
137
+ end
138
+
139
+ def current_host
140
+ ActiveStorage::Current.host
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :active_storage do
4
+ namespace :postgresql do
5
+ desc "Copy over the migration needed to the application"
6
+ task install: :environment do
7
+ if Rake::Task.task_defined?("active_storage_postgresql:install:migrations")
8
+ Rake::Task["active_storage_postgresql:install:migrations"].invoke
9
+ else
10
+ Rake::Task["app:active_storage_postgresql:install:migrations"].invoke
11
+ end
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_storage-postgresql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lachlan Sylvester
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: database_cleaner
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ description:
70
+ email:
71
+ - lachlan.sylvester@hypothetical.com.au
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - MIT-LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - db/migrate/20180530020601_create_active_storage_postgresql_tables.rb
80
+ - lib/active_storage/postgresql.rb
81
+ - lib/active_storage/postgresql/engine.rb
82
+ - lib/active_storage/postgresql/file.rb
83
+ - lib/active_storage/postgresql/version.rb
84
+ - lib/active_storage/service/postgre_sql_service.rb
85
+ - lib/active_storage/service/postgresql_service.rb
86
+ - lib/tasks/active_storage/postgresql_tasks.rake
87
+ homepage: https://github.com/lsylvester/active_storage-postgresql
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.7.3
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: PostgreSQL Adapter for Active Storage
111
+ test_files: []