paperweight 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8380f20790cf3d11b41e3da6274c68fc6c0ac692fcaeb2a5aa7c2c2641bebeff
4
- data.tar.gz: 9d992fc125eefcf29f06cd590f4e742bda84e1fc2ab47d08e8b113ac2d77d1bc
3
+ metadata.gz: c257b89200e21b312d6e80618546a84263d2f4d87408d07e2ae1d9bb86b2935b
4
+ data.tar.gz: 30997e5c1c095e24ba48ee655c8ca7f487a741c0687858d9e991c75730c3cb90
5
5
  SHA512:
6
- metadata.gz: ee438611f9a2788d970f41eb5c1cdf6c7b75ac47325a0aa603bb7a88e48a788fbbde058941cb14dcc22b1038a21c2dcc6c7151ea33498387d587c3f18eba2597
7
- data.tar.gz: '0568df48aa2c36ef68f8b24a7e04f6ec41e8ead419ceb4a3524831742236af9c718b9414096eb93d0f99602fb51a3fa650192ebeca46003d385228aec3ed4a8c'
6
+ metadata.gz: 1e63cb06b6c8257719e098666b220f04499d119e0dfa016c9054e8802d742933053bc6935c5f2b19084b4e84d9d8973a1586e80070a5e794b446ef81f3dd8648
7
+ data.tar.gz: 50048131ed8ad3430c886e2fc633730b3351777131bcac59b9b78e5ebb16c5e96ffcda5e72f148d078140d9e5cefa0176963ecbe5a4fd58199ad04fef2d5d57d
data/.gitignore CHANGED
@@ -5,5 +5,6 @@
5
5
  /doc/
6
6
  /log/
7
7
  /pkg/
8
+ /public/
8
9
  /spec/reports/
9
10
  /tmp/
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paperweight (0.0.5)
5
- aws-sdk-s3 (~> 1.13)
4
+ paperweight (0.1.0)
6
5
  paperclip (~> 6.0)
7
6
  rails (~> 5.2)
8
7
 
@@ -52,21 +51,6 @@ GEM
52
51
  tzinfo (~> 1.1)
53
52
  arel (9.0.0)
54
53
  ast (2.4.0)
55
- aws-eventstream (1.0.0)
56
- aws-partitions (1.91.0)
57
- aws-sdk-core (3.21.2)
58
- aws-eventstream (~> 1.0)
59
- aws-partitions (~> 1.0)
60
- aws-sigv4 (~> 1.0)
61
- jmespath (~> 1.0)
62
- aws-sdk-kms (1.5.0)
63
- aws-sdk-core (~> 3)
64
- aws-sigv4 (~> 1.0)
65
- aws-sdk-s3 (1.14.0)
66
- aws-sdk-core (~> 3, >= 3.21.2)
67
- aws-sdk-kms (~> 1)
68
- aws-sigv4 (~> 1.0)
69
- aws-sigv4 (1.0.2)
70
54
  builder (3.2.3)
71
55
  climate_control (0.2.0)
72
56
  concurrent-ruby (1.0.5)
@@ -78,7 +62,6 @@ GEM
78
62
  i18n (1.0.1)
79
63
  concurrent-ruby (~> 1.0)
80
64
  jaro_winkler (1.5.1)
81
- jmespath (1.4.0)
82
65
  json (2.1.0)
83
66
  loofah (2.2.2)
84
67
  crass (~> 1.0.2)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.com/CultureHQ/paperweight.svg?branch=master)](https://travis-ci.com/CultureHQ/paperweight)
4
4
 
5
- An opinionated Paperclip for a specific workflow. Only accepts image URLs instead of uploaded files. The image URLs are then queued for downloading, converting, and generating thumbnails in the background using `ActiveJob`.
5
+ Handles `Paperclip` attachments on the backend in a delayed process.
6
6
 
7
7
  ## Installation
8
8
 
@@ -22,45 +22,7 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- First, configure `paperweight` in an initializer, e.g., `config/initializers/paperweight.rb`:
26
-
27
- ```ruby
28
- Paperweight.configure do |config|
29
- # your asset server (preferably a CDN)
30
- config.asset_server = 'https://uploads.culturehq.com'
31
-
32
- # the S3 bucket to which to uploads images
33
- config.bucket = 'culturehq-uploads'
34
-
35
- # the AWS credentials used with permission to S3
36
- config.credentials = {
37
- access_key_id: Rails.application.credentials.aws_access_key_id,
38
- secret_access_key: Rails.application.credentials.aws_secret_access_key,
39
- region: 'us-west-2'
40
- }
41
- end
42
- ```
43
-
44
- Then, create and run a migration to add the `image_uuid` and `image_processing` columns to your model:
45
-
46
- ```ruby
47
- class AddImageToPost < ActiveRecord::Migration[5.2]
48
- def change
49
- add_column :posts, :image_uuid, :string
50
- add_column :posts, :image_processing, :boolean, default: false, null: false
51
- end
52
- end
53
- ```
54
-
55
- Next, add the class macro to the model:
56
-
57
- ```ruby
58
- class Post < ApplicationRecord
59
- has_image thumb: '100x100>'
60
- end
61
- ```
62
-
63
- Finally, add the ability to update the `image_url` attribute from the controller:
25
+ Configure `Paperclip` as you normally would, according to their docs. Then, add the ability to update the `*_url` attribute from the controller, where the `*` is the name of the attachment that you specified in your model's call to `has_attached_file`.
64
26
 
65
27
  ```ruby
66
28
  class PostsController < ApplicationController
@@ -83,6 +45,8 @@ class PostsController < ApplicationController
83
45
  end
84
46
  ```
85
47
 
48
+ From now on, updating this attribute will queue a job in the background to update the image later.
49
+
86
50
  ## Development
87
51
 
88
52
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -32,12 +32,6 @@ module Paperweight
32
32
  raise Error, "download failed (#{url}): #{message}"
33
33
  end
34
34
 
35
- def self.download(url)
36
- new.download(url)
37
- rescue Error
38
- nil
39
- end
40
-
41
35
  private
42
36
 
43
37
  # open-uri will return a StringIO instead of a Tempfile if the filesize
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paperweight
4
+ # Overrides the `has_attached_file` method from `paperclip` so that
5
+ # `paperweight` can add extras when the macro is called.
6
+ module Hook
7
+ # Converts a parent name to its respective component child names.
8
+ class AttachmentName
9
+ attr_reader :name
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ def processing
16
+ :"#{name}_processing"
17
+ end
18
+
19
+ def url
20
+ :"#{name}_url"
21
+ end
22
+
23
+ def url_eq
24
+ :"#{name}_url="
25
+ end
26
+
27
+ def url_attr
28
+ :"@#{name}_url"
29
+ end
30
+ end
31
+
32
+ def has_attached_file(name, *) # rubocop:disable Naming/PredicateName
33
+ super
34
+
35
+ name = AttachmentName.new(name)
36
+ attr_reader name.url
37
+
38
+ define_paperweight_setter_for(name)
39
+ define_paperweight_after_commit_for(name)
40
+ end
41
+
42
+ private
43
+
44
+ def define_paperweight_setter_for(name)
45
+ define_method(name.url_eq) do |value|
46
+ instance_variable_set(name.url_attr, value)
47
+ self[name.processing] = value ? true : false
48
+ end
49
+ end
50
+
51
+ def define_paperweight_after_commit_for(name)
52
+ after_commit if: name.url do
53
+ attachment_url = public_send(name.url)
54
+ PostProcessJob.perform_later(self, name.name.to_s, attachment_url)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paperweight
4
+ # Queues post processing.
5
+ class PostProcessJob < ActiveJob::Base
6
+ queue_as :default
7
+
8
+ discard_on ActiveJob::DeserializationError
9
+
10
+ def perform(model, name, url)
11
+ model.update!(
12
+ name => Download.new.download(url),
13
+ :"#{name}_processing" => false
14
+ )
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paperweight
4
- VERSION = '0.0.5'
4
+ VERSION = '0.1.0'
5
5
  end
data/lib/paperweight.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aws-sdk-s3'
4
3
  require 'open-uri'
5
4
  require 'net/http'
6
5
  require 'paperclip'
@@ -8,12 +7,9 @@ require 'rails'
8
7
 
9
8
  require 'active_job'
10
9
 
11
- require 'paperweight/config'
12
10
  require 'paperweight/download'
13
- require 'paperweight/image'
14
- require 'paperweight/location'
15
- require 'paperweight/purge_job'
16
- require 'paperweight/railtie'
17
- require 'paperweight/storage'
18
- require 'paperweight/thumbnails_job'
11
+ require 'paperweight/hook'
12
+ require 'paperweight/post_process_job'
19
13
  require 'paperweight/version'
14
+
15
+ Paperclip::ClassMethods.prepend(Paperweight::Hook)
data/paperweight.gemspec CHANGED
@@ -21,7 +21,6 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ['lib']
23
23
 
24
- spec.add_dependency 'aws-sdk-s3', '~> 1.13'
25
24
  spec.add_dependency 'paperclip', '~> 6.0'
26
25
  spec.add_dependency 'rails', '~> 5.2'
27
26
 
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperweight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-15 00:00:00.000000000 Z
11
+ date: 2018-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: aws-sdk-s3
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.13'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.13'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: paperclip
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -154,14 +140,9 @@ files:
154
140
  - bin/console
155
141
  - bin/setup
156
142
  - lib/paperweight.rb
157
- - lib/paperweight/config.rb
158
143
  - lib/paperweight/download.rb
159
- - lib/paperweight/image.rb
160
- - lib/paperweight/location.rb
161
- - lib/paperweight/purge_job.rb
162
- - lib/paperweight/railtie.rb
163
- - lib/paperweight/storage.rb
164
- - lib/paperweight/thumbnails_job.rb
144
+ - lib/paperweight/hook.rb
145
+ - lib/paperweight/post_process_job.rb
165
146
  - lib/paperweight/version.rb
166
147
  - paperweight.gemspec
167
148
  homepage: https://github.com/CultureHQ/paperweight
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # An opinionated Paperclip
4
- module Paperweight
5
- # Stores various metadata about the configuration of `paperweight`.
6
- class Config
7
- attr_accessor :asset_server, :bucket, :credentials
8
-
9
- def initialize
10
- @bucket = ''
11
- @credentials = { region: 'us-east-1' }
12
- end
13
- end
14
-
15
- class << self
16
- def config
17
- @config ||= Config.new
18
- end
19
-
20
- def configure
21
- yield config
22
- end
23
- end
24
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # The image object attached to the model.
5
- class Image
6
- attr_reader :model
7
-
8
- def initialize(model)
9
- @model = model
10
- end
11
-
12
- def as_json(*)
13
- serialized_styles_for(model).merge!(
14
- is_default: model.image? ? false : true,
15
- processing: model.image_processing?
16
- )
17
- end
18
-
19
- def update_file(file)
20
- image_styles.each do |style, geometry|
21
- options = { geometry: geometry, convert_options: '-strip' }
22
- thumbnail = Paperclip::Thumbnail.new(file, options).make
23
- Storage.adapter.put(thumbnail, Location.new(model, style).path)
24
- end
25
-
26
- model.clear_image_url
27
- model.update!(image_processing: false)
28
- end
29
-
30
- def update_url(url)
31
- update_file(Download.download(url))
32
- end
33
-
34
- def path(style = :original)
35
- Location.new(model, style).path
36
- end
37
-
38
- def url(style = :original)
39
- Location.new(model, style).url
40
- end
41
-
42
- private
43
-
44
- def image_styles
45
- model.class.image_styles
46
- end
47
-
48
- def serialized_styles_for(model)
49
- url_method = url_method_for(model)
50
-
51
- image_styles.each_key.each_with_object({}) do |style, serialized|
52
- serialized[:"#{style}_url"] =
53
- Location.new(model, style).public_send(url_method)
54
- end
55
- end
56
-
57
- def url_method_for(model)
58
- !model.image? || model.image_processing? ? :default_url : :url
59
- end
60
- end
61
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # The location of the attachment in terms of paths are URLs.
5
- class Location
6
- attr_reader :model, :style
7
-
8
- def initialize(model, style = :original)
9
- @model = model
10
- @style = style
11
- end
12
-
13
- def path
14
- "#{table_name}/#{model.id}-#{style}-#{model.image_uuid}"
15
- end
16
-
17
- def url
18
- "#{self.class.prefix}/#{path}"
19
- end
20
-
21
- def default_url
22
- "#{self.class.prefix}/#{table_name}/#{style}.png"
23
- end
24
-
25
- def self.prefix
26
- @prefix ||=
27
- if Rails.env.production?
28
- Paperweight.config.asset_server
29
- else
30
- 'http://localhost:3000/paperweight'
31
- end
32
- end
33
-
34
- private
35
-
36
- def table_name
37
- model.class.table_name
38
- end
39
- end
40
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # Removes all previous image files matching the given pattern.
5
- class PurgeJob < ActiveJob::Base
6
- PLACEHOLDER = ':style'
7
-
8
- queue_as :default
9
-
10
- def perform(pattern, styles)
11
- styles.each do |style|
12
- Storage.adapter.delete(pattern.gsub(PLACEHOLDER, style))
13
- end
14
- end
15
-
16
- def self.perform_later_for(model, image_uuid = nil)
17
- model.image_uuid = image_uuid if image_uuid
18
-
19
- perform_later(
20
- Location.new(model, PLACEHOLDER).path,
21
- model.class.image_styles.keys.map(&:to_s)
22
- )
23
- end
24
- end
25
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # Adds the `paperweight` hook in ActiveRecord models.
5
- class Railtie < Rails::Railtie
6
- # Extensions that need be generated from input.
7
- class DynamicExtension < Module
8
- def initialize(styles)
9
- define_method(:image_styles) { styles }
10
- end
11
- end
12
-
13
- # Extensions that are the same for each kind of attachment.
14
- module StaticExtension
15
- def clear_image_url
16
- @image_url = nil
17
- end
18
-
19
- def image
20
- Image.new(self)
21
- end
22
-
23
- def image?
24
- image_uuid
25
- end
26
-
27
- def image_url
28
- @image_url
29
- end
30
-
31
- def image_url=(url)
32
- assign_attributes(
33
- image_processing: url ? true : false,
34
- image_uuid: url ? SecureRandom.uuid : nil
35
- )
36
-
37
- @image_url = url
38
- end
39
-
40
- def self.included(base)
41
- base.after_commit do
42
- PurgeJob.perform_later_for(self, prev_image_uuid) if prev_image_uuid
43
- ThumbnailsJob.perform_later(self, image_url) if image_url
44
- end
45
-
46
- base.before_destroy if: :image? do
47
- PurgeJob.perform_later_for(self)
48
- end
49
- end
50
-
51
- private
52
-
53
- def prev_image_uuid
54
- previous_changes.fetch('image_uuid', [])[0]
55
- end
56
- end
57
-
58
- # Provides the `has_image` method to attach to models.
59
- module Hook
60
- def has_image(styles = {}) # rubocop:disable Naming/PredicateName
61
- extend DynamicExtension.new({ original: '' }.merge!(styles))
62
- include StaticExtension
63
- end
64
- end
65
-
66
- initializer 'paperweight' do
67
- ActiveSupport.on_load(:active_record) { extend Hook }
68
- end
69
- end
70
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # Handles putting and deleting files from different kinds of storage.
5
- module Storage
6
- # A storage adapter for Amazon AWS S3.
7
- class Remote
8
- attr_reader :client
9
-
10
- def initialize
11
- @client = ::Aws::S3::Client.new(Paperweight.config.credentials)
12
- end
13
-
14
- def delete(key)
15
- client.delete_object(bucket: Paperweight.config.bucket, key: key)
16
- end
17
-
18
- def put(file, key)
19
- client.put_object(
20
- acl: 'public-read',
21
- bucket: Paperweight.config.bucket,
22
- key: key,
23
- content_type: Paperclip::ContentTypeDetector.new(file.path).detect,
24
- content_disposition: 'attachment',
25
- body: file
26
- )
27
- end
28
- end
29
-
30
- # A storage adapter for a local filesystem.
31
- class Local
32
- attr_reader :root
33
-
34
- def initialize(root)
35
- @root = root
36
- end
37
-
38
- def delete(key)
39
- FileUtils.rm(File.join(root, key))
40
- end
41
-
42
- def put(file, key)
43
- filepath = File.join(root, key)
44
- FileUtils.mkdir_p(File.dirname(filepath))
45
- FileUtils.cp(file.path, filepath)
46
- end
47
- end
48
-
49
- class << self
50
- def adapter
51
- @adapter ||=
52
- case Rails.env
53
- when 'production' then Remote.new
54
- when 'development' then local_for('public')
55
- when 'test' then local_for('tmp')
56
- end
57
- end
58
-
59
- private
60
-
61
- def local_for(directory)
62
- Local.new(Rails.root.join(directory, 'paperweight').to_s.freeze)
63
- end
64
- end
65
- end
66
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paperweight
4
- # Queues thumbnails for processing.
5
- class ThumbnailsJob < ActiveJob::Base
6
- queue_as :default
7
-
8
- discard_on ActiveJob::DeserializationError
9
-
10
- def perform(model, url)
11
- model.image.update_url(url)
12
- end
13
- end
14
- end