alchemy-dragonfly-s3 3.6.6 → 4.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 07e27d0972c3f7d205fea44ca18707a60674ba3a
4
- data.tar.gz: 4cbf280831ab503c0acac159cd26bd6f397e229d
2
+ SHA256:
3
+ metadata.gz: 331b499588960ebd9ead52ad551fba5e01677654abb26e069d0b368ad16817a1
4
+ data.tar.gz: 01ba7e860a18fd8e100a488734ead2fc0da2ae4448833a65a0214c19940e0aac
5
5
  SHA512:
6
- metadata.gz: d59f579103e7976a83f252389eb0a81ca82c4c0d852ecbc8f0d81f9c0e705b36decc74576eaa1157f25257e5c193a2dd3a10aa048b1174697eafd2adf485dcd2
7
- data.tar.gz: d386023a14465cf7d6f218f24fccdf3ff40165a6a2c031698ed9d9ab034c7b107a64d9944393192a59c048d6bbc09d2271f4d076cc186692a2d4aa4744bda22d
6
+ metadata.gz: 24ef9c786345029787101b8c305f6c23b195c1721ca58db62272cc60107c96c443e45e3f40aa9b32ce04417430fe9c4e3781ae7c9701f8a6f50d34357267cd90
7
+ data.tar.gz: c5d7f63a95a2fe8d9094da62c913c0887ca855f8b48f185b6221ff32f0dd0c5139b889d3155717027895b63f8c884b01ea42fa74c5332a601738202e65449f77
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.com/AlchemyCMS/alchemy-dragonfly-s3.svg?branch=alchemy-3)](https://travis-ci.com/AlchemyCMS/alchemy-dragonfly-s3)
1
+ [![Build Status](https://travis-ci.com/AlchemyCMS/alchemy-dragonfly-s3.svg?branch=alchemy-4)](https://travis-ci.com/AlchemyCMS/alchemy-dragonfly-s3)
2
2
 
3
3
  # AlchemyCMS AWS S3
4
4
 
@@ -6,16 +6,17 @@ Adds support for file attachments and rendered Alchemy thumbnails stored on Amaz
6
6
 
7
7
  ## Alchemy Version
8
8
 
9
- This branch works with Alchemy 3.6 only.
9
+ This branch works with Alchemy 4 only.
10
10
 
11
11
  - For a Alchemy 5 compatible version use the `master` branch.
12
+ - For a Alchemy 3.6 compatible version use the `alchemy-3` branch.
12
13
 
13
14
  ## Installation
14
15
 
15
16
  Add this line to your application's Gemfile:
16
17
 
17
18
  ```ruby
18
- gem 'alchemy-dragonfly-s3', github: 'AlchemyCMS/alchemy-dragonfly-s3', branch: 'alchemy-3'
19
+ gem 'alchemy-dragonfly-s3', github: 'AlchemyCMS/alchemy-dragonfly-s3', branch: 'alchemy-4'
19
20
  ```
20
21
 
21
22
  And then execute:
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  module Dragonfly
5
5
  module S3
6
- VERSION = "3.6.6"
6
+ VERSION = "4.0.2"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy-dragonfly-s3
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.6
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-21 00:00:00.000000000 Z
11
+ date: 2020-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: alchemy_cms
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.6'
19
+ version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.6'
26
+ version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dragonfly-s3_data_store
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -67,19 +67,19 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '4.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: factory_girl_rails
70
+ name: factory_bot_rails
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '4.5'
75
+ version: '5'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '4.5'
82
+ version: '5'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: simplecov
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -117,21 +117,6 @@ extra_rdoc_files: []
117
117
  files:
118
118
  - MIT-LICENSE
119
119
  - README.md
120
- - app/assets/config/alchemy_dragonfly_s3_manifest.js
121
- - app/assets/images/alchemy/missing-image.svg
122
- - app/models/alchemy/attachment/s3_url.rb
123
- - app/models/alchemy/picture/s3_url.rb
124
- - app/models/alchemy/picture_thumb.rb
125
- - app/models/alchemy/picture_thumb/create.rb
126
- - app/models/alchemy/picture_thumb/signature.rb
127
- - app/models/alchemy/picture_thumb/uid.rb
128
- - app/models/alchemy/picture_variant.rb
129
- - app/views/alchemy/admin/attachments/show.html.erb
130
- - app/views/alchemy/admin/pictures/_picture.html.erb
131
- - app/views/alchemy/admin/pictures/_picture_to_assign.html.erb
132
- - app/views/alchemy/admin/pictures/show.html.erb
133
- - app/views/alchemy/essences/_essence_file_view.html.erb
134
- - db/migrate/1_create_alchemy_picture_thumbs.rb
135
120
  - lib/alchemy-dragonfly-s3.rb
136
121
  - lib/alchemy/attachment_monkey_patch.rb
137
122
  - lib/alchemy/dragonfly/s3/engine.rb
@@ -158,8 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
143
  - !ruby/object:Gem::Version
159
144
  version: '0'
160
145
  requirements: []
161
- rubyforge_project:
162
- rubygems_version: 2.6.14.4
146
+ rubygems_version: 3.0.3
163
147
  signing_key:
164
148
  specification_version: 4
165
149
  summary: AlchemyCMS Dragonfly S3.
@@ -1 +0,0 @@
1
- //= link alchemy/missing-image.svg
@@ -1 +0,0 @@
1
- <svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="16" height="16"><path fill="#f7f7f7" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z" class=""></path></svg>
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- class Attachment < ActiveRecord::Base
5
- class S3Url
6
- def self.call(attachment)
7
- attachment.file.remote_url
8
- end
9
- end
10
- end
11
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- class Picture < ActiveRecord::Base
5
- class S3Url
6
- attr_reader :variant
7
-
8
- # @param [Alchemy::PictureVariant]
9
- #
10
- def initialize(variant)
11
- raise ArgumentError, "Variant missing!" if variant.nil?
12
-
13
- @variant = variant
14
- end
15
-
16
- def call(*)
17
- return variant.image.remote_url unless processible_image?
18
-
19
- ::Dragonfly.app(:alchemy_pictures).remote_url_for(uid)
20
- end
21
-
22
- private
23
-
24
- def processible_image?
25
- variant.image.is_a?(::Dragonfly::Job)
26
- end
27
-
28
- def uid
29
- signature = PictureThumb::Signature.call(variant)
30
- thumb = variant.picture.thumbs.detect { |t| t.signature == signature }
31
- if thumb
32
- uid = thumb.uid
33
- else
34
- uid = PictureThumb::Uid.call(signature, variant)
35
- PictureThumb::Create.call(variant, signature, uid)
36
- end
37
- uid
38
- end
39
- end
40
- end
41
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- # The persisted version of a rendered picture variant
5
- #
6
- class PictureThumb < ActiveRecord::Base
7
- belongs_to :picture, class_name: "Alchemy::Picture"
8
-
9
- validates :signature, presence: true
10
- validates :uid, presence: true
11
-
12
- class << self
13
- # Upfront generation of picture thumbnails
14
- #
15
- # Called after a Alchemy::Picture has been created (after an image has been uploaded)
16
- #
17
- # Generates three types of thumbnails that are used by Alchemys picture archive and
18
- # persists them in the configures file store (Default Dragonfly::FileDataStore).
19
- #
20
- # @see Picture::THUMBNAIL_SIZES
21
- def generate_thumbs!(picture)
22
- Alchemy::Picture::THUMBNAIL_SIZES.values.map do |size|
23
- variant = Alchemy::PictureVariant.new(picture, {
24
- size: size,
25
- flatten: true,
26
- })
27
- signature = Alchemy::PictureThumb::Signature.call(variant)
28
- thumb = find_by(signature: signature)
29
- next if thumb
30
-
31
- uid = Alchemy::PictureThumb::Uid.call(signature, variant)
32
- Alchemy::PictureThumb::Create.call(variant, signature, uid)
33
- uid
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,28 +0,0 @@
1
- module Alchemy
2
- class PictureThumb < ActiveRecord::Base
3
- # Stores the render result of a Alchemy::PictureVariant
4
- # in the Dragonfly S3 datastore
5
- #
6
- class Create
7
- def self.call(variant, signature, uid)
8
- # create the thumb before uploading
9
- # to prevent db race conditions
10
- thumb = variant.picture.thumbs.create!(
11
- picture: variant.picture,
12
- signature: signature,
13
- uid: uid,
14
- )
15
- begin
16
- # fetch and process the image
17
- image = variant.image
18
- # upload the processed image
19
- image.store(path: uid)
20
- rescue RuntimeError, Excon::Error => e
21
- Rails.logger.warn(e)
22
- # destroy the thumb if processing or upload fails
23
- thumb.destroy
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- class PictureThumb < ActiveRecord::Base
5
- class Signature
6
- # Returns a unique image process signature
7
- #
8
- # @param [Alchemy::PictureVariant]
9
- #
10
- # @return [String]
11
- def self.call(variant)
12
- steps_without_fetch = variant.image.steps.reject do |step|
13
- step.is_a?(::Dragonfly::Job::Fetch)
14
- end
15
-
16
- steps_with_id = [[variant.picture.id]] + steps_without_fetch
17
- job_string = steps_with_id.map(&:to_a).to_dragonfly_unique_s
18
-
19
- Digest::SHA1.hexdigest(job_string)
20
- end
21
- end
22
- end
23
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Alchemy
4
- class PictureThumb < ActiveRecord::Base
5
- class Uid
6
- # Returns a image variant uid for storage
7
- #
8
- # @param [String]
9
- # @param [Alchemy::PictureVariant]
10
- #
11
- # @return [String]
12
- def self.call(signature, variant)
13
- picture = variant.picture
14
- filename = variant.image_file_name || "image"
15
- name = File.basename(filename, ".*").gsub(/[^\w.]+/, "_")
16
- ext = variant.render_format
17
-
18
- "pictures/#{picture.id}/#{signature}/#{name}.#{ext}"
19
- end
20
- end
21
- end
22
- end
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "forwardable"
4
-
5
- module Alchemy
6
- # Represents a rendered picture
7
- #
8
- # Resizes, crops and encodes the image with imagemagick
9
- #
10
- class PictureVariant
11
- extend Forwardable
12
-
13
- include Alchemy::Logger
14
- include Alchemy::Picture::Transformations
15
-
16
- attr_reader :picture, :render_format
17
-
18
- def_delegators :@picture,
19
- :image_file,
20
- :image_file_width,
21
- :image_file_height,
22
- :image_file_name,
23
- :image_file_size
24
-
25
- # @param [Alchemy::Picture]
26
- #
27
- # @param [Hash] options passed to the image processor
28
- # @option options [Boolean] :crop Pass true to enable cropping
29
- # @option options [String] :crop_from Coordinates to start cropping from
30
- # @option options [String] :crop_size Size of the cropping area
31
- # @option options [Boolean] :flatten Pass true to flatten GIFs
32
- # @option options [String|Symbol] :format Image format to encode the image in
33
- # @option options [Integer] :quality JPEG compress quality
34
- # @option options [String] :size Size of resulting image in WxH
35
- # @option options [Boolean] :upsample Pass true to upsample (grow) an image if the original size is lower than the resulting size
36
- #
37
- def initialize(picture, options = {})
38
- raise ArgumentError, "Picture missing!" if picture.nil?
39
-
40
- @picture = picture
41
- @options = options
42
- @render_format = options[:format] || picture.default_render_format
43
- end
44
-
45
- # Process a variant of picture
46
- #
47
- # @return [Dragonfly::Attachment|Dragonfly::Job] The processed image variant
48
- #
49
- def image
50
- image = image_file
51
-
52
- raise MissingImageFileError, "Missing image file for #{picture.inspect}" if image.nil?
53
-
54
- image = processed_image(image, @options)
55
- image = encoded_image(image, @options)
56
- image
57
- rescue MissingImageFileError, WrongImageFormatError => e
58
- log_warning(e.message)
59
- nil
60
- end
61
-
62
- private
63
-
64
- # Returns the processed image dependent of size and cropping parameters
65
- def processed_image(image, options = {})
66
- size = options[:size]
67
- upsample = !!options[:upsample]
68
-
69
- return image unless size.present? && picture.has_convertible_format?
70
-
71
- if options[:crop]
72
- crop(size, options[:crop_from], options[:crop_size], upsample)
73
- else
74
- resize(size, upsample)
75
- end
76
- end
77
-
78
- # Returns the encoded image
79
- #
80
- # Flatten animated gifs, only if converting to a different format.
81
- # Can be overwritten via +options[:flatten]+.
82
- #
83
- def encoded_image(image, options = {})
84
- unless render_format.in?(Alchemy::Picture.allowed_filetypes)
85
- raise WrongImageFormatError.new(picture, @render_format)
86
- end
87
-
88
- options = {
89
- flatten: render_format != "gif" && picture.image_file_format == "gif",
90
- }.with_indifferent_access.merge(options)
91
-
92
- encoding_options = []
93
-
94
- convert_format = render_format.sub("jpeg", "jpg") != picture.image_file_format.sub("jpeg", "jpg")
95
-
96
- if render_format =~ /jpe?g/ && convert_format
97
- quality = options[:quality] || Config.get(:output_image_jpg_quality)
98
- encoding_options << "-quality #{quality}"
99
- end
100
-
101
- if options[:flatten]
102
- encoding_options << "-flatten"
103
- end
104
-
105
- convertion_needed = convert_format || encoding_options.present?
106
-
107
- if picture.has_convertible_format? && convertion_needed
108
- image = image.encode(render_format, encoding_options.join(" "))
109
- end
110
-
111
- image
112
- end
113
- end
114
- end
@@ -1,55 +0,0 @@
1
- <div class="resource_info">
2
- <div class="value">
3
- <label>
4
- <%= render_icon @attachment.icon_css_class %>
5
- </label>
6
- <p><%= @attachment.file_name %></p>
7
- </div>
8
- <div class="value with-icon">
9
- <label><%= Alchemy::Attachment.human_attribute_name(:url) %></label>
10
- <p><%= @attachment.url %></p>
11
- <a data-clipboard-text="<%= @attachment.url %>" class="icon_button--right">
12
- <%= render_icon('clipboard full') %>
13
- </a>
14
- </div>
15
- <div class="value with-icon">
16
- <label><%= Alchemy::Attachment.human_attribute_name(:download_url) %></label>
17
- <p><%= @attachment.url %></p>
18
- <a data-clipboard-text="<%= @attachment.url %>" class="icon_button--right">
19
- <%= render_icon('clipboard full') %>
20
- </a>
21
- </div>
22
- </div>
23
-
24
- <% case @attachment.icon_css_class %>
25
- <% when "image" %>
26
- <div class="attachment_preview_container image-preview">
27
- <%= image_tag(@attachment.url, class: "full_width") %>
28
- </div>
29
- <% when "audio" %>
30
- <div class="attachment_preview_container player-preview">
31
- <%= audio_tag(@attachment.url, preload: "none", controls: true, class: "full_width") %>
32
- </div>
33
- <% when "video" %>
34
- <div class="attachment_preview_container player-preview">
35
- <%= video_tag(@attachment.url, preload: "metadata", controls: true, class: "full_width") %>
36
- </div>
37
- <% when "archive", "file" %>
38
- <% else %>
39
- <iframe src="<%= @attachment.url %>" frameborder=0 class="full-iframe">
40
- Your browser does not support frames.
41
- </iframe>
42
- <% end %>
43
-
44
- <script type="text/javascript">
45
- $(function() {
46
- var clipboard = new Clipboard('.icon_button--right');
47
- clipboard.on('success', function(e) {
48
- Alchemy.growl('<%= Alchemy.t("Copied to clipboard") %>');
49
- e.clearSelection();
50
- });
51
- Alchemy.currentDialog().dialog.on('DialogClose.Alchemy', function() {
52
- clipboard.destroy();
53
- });
54
- });
55
- </script>
@@ -1,53 +0,0 @@
1
- <div class="picture_thumbnail <%= @size %>" id="picture_<%= picture.id %>" name="<%= picture.name %>">
2
- <span class="picture_tool select">
3
- <%= check_box_tag "picture_ids[]", picture.id %>
4
- </span>
5
- <% if picture.deletable? && can?(:destroy, picture) %>
6
- <span class="picture_tool delete">
7
- <%= link_to_confirm_dialog(
8
- "",
9
- Alchemy.t(:confirm_to_delete_image_from_server),
10
- alchemy.admin_picture_path(
11
- id: picture,
12
- q: params[:q],
13
- page: params[:page],
14
- tagged_with: params[:tagged_with],
15
- size: params[:size],
16
- filter: params[:filter]
17
- ),
18
- {
19
- title: Alchemy.t('Delete image')
20
- }
21
- ) -%>
22
- </span>
23
- <% end %>
24
- <% image = image_tag(
25
- picture.url(size: preview_size(@size), flatten: true) || "alchemy/missing-image.svg",
26
- alt: picture.name,
27
- title: Alchemy.t(:zoom_image)
28
- ) %>
29
- <% if can?(:edit, picture) %>
30
- <%= link_to(
31
- image,
32
- alchemy.admin_picture_path(
33
- id: picture,
34
- q: params[:q],
35
- page: params[:page],
36
- tagged_with: params[:tagged_with],
37
- size: params[:size],
38
- filter: params[:filter]
39
- ),
40
- class: 'thumbnail_background'
41
- ) %>
42
- <% else %>
43
- <%= image %>
44
- <% end %>
45
- <span class="picture_name" title="<%= picture.name %>">
46
- <%= picture.name %>
47
- </span>
48
- <div class="picture_tags">
49
- <% picture.tag_list.each do |tag| %>
50
- <span class="tag"><%= tag %></span>
51
- <% end %>
52
- </div>
53
- </div>
@@ -1,18 +0,0 @@
1
- <div class="picture_thumbnail assign_image_list_detail <%= size.blank? ? 'medium' : size %>" name="<%= picture_to_assign.name %>" id="picture_to_assign_<%= picture_to_assign.id %>">
2
- <% action_url = create_or_assign_url(picture_to_assign, @options.to_json) %>
3
- <%= link_to(
4
- image_tag(
5
- picture_to_assign.url(size: preview_size(size), flatten: true) || "alchemy/missing-image.svg",
6
- alt: picture_to_assign.name
7
- ),
8
- action_url,
9
- remote: true,
10
- onclick: '$(self).attr("href", "#").off("click"); return false',
11
- method: @content.blank? ? 'post' : 'put',
12
- title: Alchemy.t(:assign_image),
13
- class: 'thumbnail_background'
14
- ) %>
15
- <div class="picture_name" title="<%= picture_to_assign.name %>">
16
- <%= picture_to_assign.name %>
17
- </div>
18
- </div>
@@ -1,43 +0,0 @@
1
- <div class="zoomed-picture-background">
2
- <%= image_tag @picture.url || "alchemy/missing-image.svg" %>
3
- </div>
4
-
5
- <div class="picture-overlay-navigation">
6
- <% if @previous %>
7
- <%= link_to alchemy.admin_picture_path(
8
- id: @previous,
9
- q: params[:q],
10
- page: params[:page],
11
- tagged_with: params[:tagged_with],
12
- size: params[:size],
13
- filter: params[:filter]
14
- ),
15
- class: "previous-picture",
16
- remote: true do %>
17
- <span class="icon-angle-left"></span>
18
- <% end %>
19
- <% end %>
20
- <% if @next %>
21
- <%= link_to alchemy.admin_picture_path(
22
- id: @next,
23
- q: params[:q],
24
- page: params[:page],
25
- tagged_with: params[:tagged_with],
26
- size: params[:size],
27
- filter: params[:filter]
28
- ),
29
- class: "next-picture",
30
- remote: true do %>
31
- <span class="icon-angle-right"></span>
32
- <% end %>
33
- <% end %>
34
- </div>
35
-
36
- <div class="picture-details-overlay">
37
- <%= render 'form' %>
38
- <%= render 'infos' %>
39
- </div>
40
-
41
- <div class="picture-overlay-handle">
42
- <span class="icon-angle-double-right"></span>
43
- </div>
@@ -1,13 +0,0 @@
1
- <%- if attachment = content.ingredient -%>
2
- <%- html_options = local_assigns.fetch(:html_options, {}) -%>
3
- <%= link_to(
4
- content.essence.link_text.presence ||
5
- content.settings_value(:link_text, local_assigns.fetch(:options, {})) ||
6
- attachment.name,
7
- attachment.url,
8
- {
9
- class: content.essence.css_class.presence,
10
- title: content.essence.title.presence
11
- }.merge(html_options)
12
- ) -%>
13
- <%- end -%>
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateAlchemyPictureThumbs < ActiveRecord::Migration
4
- def change
5
- create_table :alchemy_picture_thumbs do |t|
6
- t.integer :picture_id, null: false, index: true
7
- t.string :signature, null: false
8
- t.text :uid, null: false
9
- end
10
- add_foreign_key :alchemy_picture_thumbs, :alchemy_pictures, column: :picture_id
11
- add_index :alchemy_picture_thumbs, :signature, unique: true
12
- end
13
- end