attachs 0.4.5 → 4.0.0.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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +83 -218
  4. data/Rakefile +1 -14
  5. data/lib/attachs.rb +98 -31
  6. data/lib/attachs/attachment.rb +301 -72
  7. data/lib/attachs/builder.rb +78 -0
  8. data/lib/attachs/collection.rb +84 -0
  9. data/lib/attachs/concern.rb +47 -0
  10. data/lib/attachs/configuration.rb +11 -0
  11. data/lib/attachs/console.rb +40 -0
  12. data/lib/attachs/extensions/active_record/base.rb +24 -0
  13. data/lib/attachs/extensions/active_record/validations/attachment_content_type_validator.rb +37 -0
  14. data/lib/attachs/extensions/active_record/validations/attachment_presence_validator.rb +27 -0
  15. data/lib/attachs/extensions/active_record/validations/attachment_size_validator.rb +55 -0
  16. data/lib/attachs/extensions/active_record/validations/attachment_validator.rb +24 -0
  17. data/lib/attachs/interpolations.rb +27 -0
  18. data/lib/attachs/jobs/base.rb +14 -0
  19. data/lib/attachs/jobs/delete_job.rb +15 -0
  20. data/lib/attachs/jobs/update_job.rb +11 -0
  21. data/lib/attachs/locales/en.yml +2 -6
  22. data/lib/attachs/locales/es.yml +2 -6
  23. data/lib/attachs/processors/base.rb +4 -14
  24. data/lib/attachs/processors/image.rb +45 -0
  25. data/lib/attachs/railtie.rb +7 -27
  26. data/lib/attachs/storages/base.rb +10 -59
  27. data/lib/attachs/storages/s3.rb +68 -63
  28. data/lib/attachs/version.rb +1 -1
  29. data/lib/generators/attachs/install/install_generator.rb +15 -0
  30. data/lib/generators/attachs/install/templates/initializer.rb +7 -0
  31. data/lib/generators/attachs/upload/templates/migration.rb +11 -0
  32. data/lib/generators/attachs/upload/templates/model.rb +19 -0
  33. data/lib/generators/attachs/upload/upload_generator.rb +24 -0
  34. data/lib/tasks/attachs.rake +12 -26
  35. data/test/attachment_test.rb +175 -12
  36. data/test/collection_test.rb +52 -0
  37. data/test/concern_test.rb +10 -0
  38. data/test/dummy/Rakefile +1 -2
  39. data/test/dummy/app/assets/javascripts/application.js +2 -2
  40. data/test/dummy/app/assets/stylesheets/application.css +6 -4
  41. data/test/dummy/app/models/product.rb +16 -0
  42. data/test/dummy/app/models/shop.rb +14 -0
  43. data/test/dummy/app/models/upload.rb +19 -0
  44. data/test/dummy/app/views/layouts/application.html.erb +9 -11
  45. data/test/dummy/bin/bundle +1 -0
  46. data/test/dummy/bin/rails +2 -1
  47. data/test/dummy/bin/rake +1 -0
  48. data/test/dummy/bin/setup +30 -0
  49. data/test/dummy/config.ru +1 -1
  50. data/test/dummy/config/application.rb +3 -2
  51. data/test/dummy/config/boot.rb +1 -1
  52. data/test/dummy/config/database.yml +4 -22
  53. data/test/dummy/config/database.yml.travis +3 -0
  54. data/test/dummy/config/environment.rb +1 -1
  55. data/test/dummy/config/environments/development.rb +15 -3
  56. data/test/dummy/config/environments/production.rb +21 -26
  57. data/test/dummy/config/environments/test.rb +10 -12
  58. data/test/dummy/config/initializers/assets.rb +11 -0
  59. data/test/dummy/config/initializers/attachs.rb +7 -21
  60. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  61. data/test/dummy/config/initializers/mime_types.rb +1 -2
  62. data/test/dummy/config/initializers/session_store.rb +1 -1
  63. data/test/dummy/config/routes.rb +54 -0
  64. data/test/dummy/config/secrets.yml +24 -0
  65. data/test/dummy/db/migrate/20161024234003_create_shops.rb +11 -0
  66. data/test/dummy/db/migrate/20161024234029_create_products.rb +12 -0
  67. data/test/dummy/db/migrate/20161031135453_create_uploads.rb +11 -0
  68. data/test/dummy/db/schema.rb +27 -16
  69. data/test/dummy/log/development.log +61 -0
  70. data/test/dummy/log/test.log +5873 -0
  71. data/test/dummy/public/404.html +58 -55
  72. data/test/dummy/public/422.html +58 -55
  73. data/test/dummy/public/500.html +57 -54
  74. data/test/fixtures/big_file.txt +50 -0
  75. data/test/fixtures/big_image.jpg +0 -0
  76. data/test/fixtures/file.txt +1 -0
  77. data/test/fixtures/image.jpg +0 -0
  78. data/test/{dummy/test/fixtures/file.txt → fixtures/small_file.txt} +0 -0
  79. data/test/fixtures/small_image.jpg +0 -0
  80. data/test/generator_test.rb +14 -6
  81. data/test/helper_test.rb +28 -0
  82. data/test/query_test.rb +26 -0
  83. data/test/support/storage_helper.rb +65 -0
  84. data/test/task_test.rb +69 -0
  85. data/test/test_helper.rb +6 -45
  86. data/test/upload_test.rb +27 -0
  87. data/test/validator_test.rb +160 -0
  88. metadata +114 -68
  89. data/lib/attachs/active_record/base.rb +0 -103
  90. data/lib/attachs/active_record/connection_adapters.rb +0 -40
  91. data/lib/attachs/active_record/migration.rb +0 -17
  92. data/lib/attachs/active_record/validators.rb +0 -7
  93. data/lib/attachs/active_record/validators/attachment_content_type_validator.rb +0 -30
  94. data/lib/attachs/active_record/validators/attachment_presence_validator.rb +0 -22
  95. data/lib/attachs/active_record/validators/attachment_size_validator.rb +0 -47
  96. data/lib/attachs/processors/thumbnail.rb +0 -69
  97. data/lib/attachs/storages/local.rb +0 -95
  98. data/lib/attachs/types/base.rb +0 -22
  99. data/lib/attachs/types/default.rb +0 -29
  100. data/lib/attachs/types/regular.rb +0 -21
  101. data/lib/generators/attachs/install_generator.rb +0 -13
  102. data/lib/generators/attachs/templates/attachs.rb +0 -2
  103. data/test/dummy/README.rdoc +0 -28
  104. data/test/dummy/app/models/medium.rb +0 -5
  105. data/test/dummy/app/models/picture.rb +0 -2
  106. data/test/dummy/config/initializers/secret_token.rb +0 -13
  107. data/test/dummy/config/s3.yml +0 -15
  108. data/test/dummy/db/migrate/20140808012639_create_media.rb +0 -11
  109. data/test/dummy/test/fixtures/image.gif +0 -0
  110. data/test/local_storage_test.rb +0 -72
  111. data/test/migration_test.rb +0 -65
  112. data/test/processor_test.rb +0 -49
  113. data/test/s3_storage_tes.rb +0 -66
  114. data/test/tasks_test.rb +0 -57
  115. data/test/validators_test.rb +0 -100
@@ -1,9 +1,5 @@
1
1
  es:
2
2
  errors:
3
3
  messages:
4
- attachment_presence: "no puede estar vacío"
5
- attachment_content_type_with: "tiene un formato no permitido"
6
- attachment_content_type_in: "debe ser de uno de los siguientes formatos: %{types}"
7
- attachment_size_less_than: "debe pesar menos de %{count}"
8
- attachment_size_greater_than: "debe pesar más de %{count}"
9
- attachment_size_in: "debe pesar entre %{min} y %{max}"
4
+ not_allowed: "no permitido"
5
+ not_listed: "debe ser de uno de los siguientes: %{list}"
@@ -2,23 +2,13 @@ module Attachs
2
2
  module Processors
3
3
  class Base
4
4
 
5
- def initialize(attachment, source)
6
- @attachment = attachment
7
- @source = source
5
+ def initialize(source_path)
6
+ @source_path = source_path
8
7
  end
9
8
 
10
- protected
9
+ private
11
10
 
12
- attr_reader :attachment, :source
13
-
14
- def run(cmd)
15
- stdout, stderr, status = Open3.capture3(cmd)
16
- if status.success?
17
- stdout.strip
18
- else
19
- false
20
- end
21
- end
11
+ attr_reader :source_path
22
12
 
23
13
  end
24
14
  end
@@ -0,0 +1,45 @@
1
+ module Attachs
2
+ module Processors
3
+ class Image < Base
4
+
5
+ def process(destination_path, geometry)
6
+ if geometry
7
+ size, suffix = geometry.scan(/([^!#]+)(!|#)?$/).flatten
8
+ strategy = detect_strategy(suffix)
9
+ new_width, new_height = size.split('x').map(&:to_i)
10
+ resize new_width, new_height, strategy, destination_path
11
+ else
12
+ Console.convert source_path, destination_path
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def detect_strategy(suffix)
19
+ case suffix
20
+ when '#'
21
+ :cover
22
+ when '!'
23
+ :force
24
+ else
25
+ :contain
26
+ end
27
+ end
28
+
29
+ def resize(new_width, new_height, strategy, destination_path)
30
+ options = [Attachs.configuration.convert_options]
31
+ case strategy
32
+ when :cover
33
+ options << "-resize #{new_width}x#{new_height}^ -gravity center"
34
+ options << "-crop #{new_width}x#{new_height}+0+0 +repage"
35
+ when :force
36
+ options << "-resize #{new_width}x#{new_height}\!"
37
+ when :contain
38
+ options << "-resize #{new_width}x#{new_height}"
39
+ end
40
+ Console.convert source_path, destination_path, options.join(' ')
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -1,24 +1,14 @@
1
1
  module Attachs
2
2
  class Railtie < Rails::Railtie
3
3
 
4
- initializer 'attachs' do
5
- ::ActiveRecord::Base.send(
6
- :include,
7
- Attachs::ActiveRecord::Base,
8
- Attachs::ActiveRecord::Validators
9
- )
10
- ::ActiveRecord::ConnectionAdapters::TableDefinition.send(
11
- :include,
12
- Attachs::ActiveRecord::ConnectionAdapters::TableDefinition
13
- )
14
- ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
15
- :include,
16
- Attachs::ActiveRecord::ConnectionAdapters::AbstractAdapter
17
- )
18
- ::ActiveRecord::Migration::CommandRecorder.send(
19
- :include,
20
- Attachs::ActiveRecord::Migration::CommandRecorder
4
+ initializer 'attachs.extensions' do
5
+ ::ActiveRecord::Base.include(
6
+ Attachs::Extensions::ActiveRecord::Base,
7
+ Attachs::Extensions::ActiveRecord::Validations
21
8
  )
9
+ end
10
+
11
+ initializer 'attachs.locales' do
22
12
  I18n.load_path += Dir[File.expand_path('../locales/*.yml', __FILE__)]
23
13
  end
24
14
 
@@ -26,15 +16,5 @@ module Attachs
26
16
  load 'tasks/attachs.rake'
27
17
  end
28
18
 
29
- config.after_initialize do
30
- if Attachs.config.s3[:access_key_id] and Attachs.config.s3[:secret_access_key]
31
- require 'aws-sdk'
32
- AWS.config(
33
- access_key_id: Attachs.config.s3[:access_key_id],
34
- secret_access_key: Attachs.config.s3[:secret_access_key]
35
- )
36
- end
37
- end
38
-
39
19
  end
40
20
  end
@@ -2,71 +2,22 @@ module Attachs
2
2
  module Storages
3
3
  class Base
4
4
 
5
- def initialize(attachment)
6
- @attachment = attachment
7
- end
8
-
9
- protected
5
+ private
10
6
 
11
- attr_reader :attachment
12
-
13
- def template
14
- @template ||= begin
15
- if attachment.exists?
16
- (attachment.options[:path] || Attachs.config.default_path).dup
17
- else
18
- attachment.options[:default_path].dup
19
- end.tap do |path|
20
- path.scan(/:([a-zA-Z0-9_]+)/).flatten.uniq.map(&:to_sym).each do |name|
21
- if name != :style
22
- path.gsub! ":#{name}", interpolate(name)
23
- end
24
- end
25
- path.squeeze! '/'
26
- path.slice! 0 if path[0] == '/'
27
- end
28
- end
7
+ def detect_content_type(path)
8
+ MIME::Types.type_for(path).first.to_s
29
9
  end
30
10
 
31
- def path(style=:original)
32
- template.gsub(':style', style.to_s)
33
- end
34
-
35
- def interpolate(name)
36
- if interpolation = Attachs.config.interpolations[name]
37
- interpolation.call(attachment).to_s.parameterize
38
- else
39
- case name
40
- when :basename
41
- attachment.basename.parameterize
42
- when :filename
43
- "#{attachment.basename.parameterize}.#{attachment.extension}"
44
- when :size
45
- attachment.size
46
- when :extension
47
- attachment.extension
48
- when :type
49
- attachment.content_type.split('/').first.parameterize
50
- when :timestamp
51
- attachment.updated_at.to_i
52
- when :class
53
- attachment.record.class.name.parameterize
54
- when :id
55
- attachment.record.id
56
- when :attribute
57
- attachment.attribute.to_s.parameterize
58
- end.to_s
59
- end
11
+ def processable?(path)
12
+ detect_content_type(path) =~ /^image\//
60
13
  end
61
14
 
62
- def find_option(options, name, default)
63
- if options.has_key?(name)
64
- options[name]
65
- elsif attachment.options.has_key?(name)
66
- attachment.options[name]
67
- else
68
- default
15
+ def build_processor(path)
16
+ case detect_content_type(path)
17
+ when /^image\//
18
+ klass = Processors::Image
69
19
  end
20
+ klass.new path
70
21
  end
71
22
 
72
23
  end
@@ -2,90 +2,95 @@ module Attachs
2
2
  module Storages
3
3
  class S3 < Base
4
4
 
5
- def url(*args)
6
- if attachment.url?
7
- options = args.extract_options!
8
- style = (args[0] || :original)
9
- if Attachs.config.base_url.present?
10
- Pathname.new(Attachs.config.base_url).join(path(style)).to_s
11
- else
12
- object(style).public_url(secure: find_option(options, :ssl, Attachs.config.s3[:ssl])).to_s
13
- end.tap do |url|
14
- if find_option(options, :cachebuster, Attachs.config.cachebuster)
15
- url << "?#{attachment.updated_at.to_i}"
16
- end
17
- end
5
+ def url(path)
6
+ base_url = Attachs.configuration.base_url
7
+ if base_url.present?
8
+ Pathname.new(base_url).join(path).to_s
9
+ else
10
+ bucket.object(path).public_url
18
11
  end
19
12
  end
20
13
 
21
- def process(force=false)
22
- if attachment.upload?
23
- stream attachment.upload, path
24
- attachment.uploaded!
14
+ def process(file, paths, options)
15
+ if processable?(file.path)
16
+ processor = build_processor(file.path)
17
+ paths.each do |style, path|
18
+ tmp = build_tempfile(path)
19
+ processor.process tmp.path, options[style]
20
+ upload tmp.path, path
21
+ end
22
+ else
23
+ upload file.path, paths[:original]
25
24
  end
26
- process_styles force
27
25
  end
28
26
 
29
- def process_styles(force=false)
30
- if attachment.image?
31
- unless cache[path]
32
- download = Tempfile.new('s3')
33
- object.read do |chunk|
34
- download.write chunk
35
- end
36
- cache[path] = download
37
- end
38
- attachment.processors.each do |klass|
39
- processor = klass.new(attachment, cache[path].path)
40
- attachment.styles.each do |style|
41
- if force == true
42
- object(style).delete
43
- end
44
- unless object(style).exists?
45
- tmp = Tempfile.new('s3')
46
- processor.process style, tmp.path
47
- stream tmp, path(style)
48
- end
49
- end
50
- end
27
+ def get(path)
28
+ file = build_tempfile(path)
29
+ file.binmode
30
+ bucket.object(path).get do |chunk|
31
+ file.write chunk
51
32
  end
33
+ file.rewind
34
+ file.close
35
+ file
52
36
  end
53
37
 
54
- def destroy
55
- object.delete
56
- destroy_styles
38
+ def copy(current_path, new_path)
39
+ Rails.logger.info "Copying: #{current_path} => #{new_path}"
40
+ bucket.object(current_path).copy_to bucket.object(new_path), acl: 'public-read'
57
41
  end
58
42
 
59
- def destroy_styles
60
- if attachment.image?
61
- attachment.styles.each do |style|
62
- object(style).delete
63
- end
64
- end
43
+ def move(current_path, new_path)
44
+ Rails.logger.info "Moving: #{current_path} => #{new_path}"
45
+ bucket.object(current_path).move_to bucket.object(new_path)
65
46
  end
66
47
 
67
- protected
48
+ def delete(path)
49
+ Rails.logger.info "Deleting: #{path}"
50
+ bucket.object(path).delete
51
+ end
52
+
53
+ def exist?(path)
54
+ bucket.object(path).exists?
55
+ end
68
56
 
69
- def cache
70
- @cache ||= {}
57
+ def find_each
58
+ bucket.objects.each do |object|
59
+ yield object.key
60
+ end
71
61
  end
72
62
 
63
+ private
64
+
73
65
  def bucket
74
- @bucket ||= AWS::S3.new.buckets[Attachs.config.s3[:bucket]]
66
+ @bucket ||= begin
67
+ require 'aws-sdk'
68
+ credentials = Aws::Credentials.new(
69
+ Rails.application.secrets.aws_access_key_id,
70
+ Rails.application.secrets.aws_secret_access_key
71
+ )
72
+ Aws.config.update(
73
+ region: Attachs.configuration.region,
74
+ credentials: credentials
75
+ )
76
+ Aws::S3::Resource.new.bucket Attachs.configuration.bucket
77
+ end
75
78
  end
76
79
 
77
- def object(style=:original)
78
- bucket.objects[path(style)]
80
+ def build_tempfile(path)
81
+ extension = File.extname(path)
82
+ Tempfile.new ['s3', extension]
79
83
  end
80
84
 
81
- def stream(file, path)
82
- object = bucket.objects.create(path, File.open(file.path, 'rb'))
83
- if attachment.private?
84
- object.acl = :private
85
- else
86
- object.acl = :public_read
87
- end
88
- cache[path] = file
85
+ def upload(local_path, remote_path)
86
+ Rails.logger.info "Uploading: #{local_path} => #{remote_path}"
87
+ bucket.object(remote_path).upload_file(
88
+ local_path,
89
+ acl: 'public-read',
90
+ content_type: detect_content_type(remote_path),
91
+ cache_control: 'max-age=315360000, public',
92
+ expires: Time.parse('31 Dec 2037 23:55:55 GMT').httpdate
93
+ )
89
94
  end
90
95
 
91
96
  end
@@ -1,5 +1,5 @@
1
1
  module Attachs
2
2
 
3
- VERSION = '0.4.5'
3
+ VERSION = '4.0.0.0'
4
4
 
5
5
  end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+
3
+ module Attachs
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def create_initializer_file
10
+ copy_file 'initializer.rb', 'config/initializers/attachs.rb'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ Attachs.configure do |config|
2
+
3
+ config.convert_options = '-strip -quality 82'
4
+ config.region = 'us-east-1'
5
+ config.bucket = 'some-bucket'
6
+
7
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUploads < ActiveRecord::Migration
2
+ def change
3
+ create_table :uploads do |t|
4
+ t.attachment :file
5
+ t.string :record_type
6
+ t.string :record_attribute
7
+
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ class Upload < ActiveRecord::Base
2
+
3
+ has_attachment(
4
+ :file,
5
+ path: 'uploads/:id-:style.:extension',
6
+ styles: ->(record) {
7
+ record.model.attachments[record.record_attribute.to_sym][:styles]
8
+ }
9
+ )
10
+
11
+ validates_presence_of :file, :record_type, :record_attribute
12
+
13
+ def model
14
+ if record_type
15
+ record_type.classify.constantize
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+
3
+ module Attachs
4
+ module Generators
5
+ class UploadGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_model_file
11
+ copy_file 'model.rb', 'app/models/upload.rb'
12
+ end
13
+
14
+ def create_migration_file
15
+ migration_template 'migration.rb', 'db/migrate/create_uploads.rb'
16
+ end
17
+
18
+ def self.next_migration_number(path)
19
+ Time.now.utc.strftime '%Y%m%d%H%M%S'
20
+ end
21
+
22
+ end
23
+ end
24
+ end