attachs 0.3.5 → 0.4.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +254 -0
  4. data/Rakefile +0 -2
  5. data/lib/attachs.rb +37 -10
  6. data/lib/attachs/active_record/base.rb +58 -95
  7. data/lib/attachs/active_record/connection_adapters.rb +40 -0
  8. data/lib/attachs/active_record/migration.rb +17 -0
  9. data/lib/attachs/active_record/validators.rb +7 -0
  10. data/lib/attachs/active_record/validators/attachment_content_type_validator.rb +30 -0
  11. data/lib/attachs/active_record/validators/attachment_presence_validator.rb +22 -0
  12. data/lib/attachs/active_record/validators/attachment_size_validator.rb +39 -0
  13. data/lib/attachs/attachment.rb +114 -0
  14. data/{config → lib/attachs}/locales/en.yml +2 -1
  15. data/{config → lib/attachs}/locales/es.yml +2 -1
  16. data/lib/attachs/railtie.rb +31 -11
  17. data/lib/attachs/storages/base.rb +68 -0
  18. data/lib/attachs/storages/local.rb +61 -37
  19. data/lib/attachs/storages/s3.rb +66 -57
  20. data/lib/attachs/tools/magick.rb +52 -0
  21. data/lib/attachs/types/base.rb +22 -0
  22. data/lib/attachs/types/default.rb +21 -0
  23. data/lib/attachs/types/regular.rb +21 -0
  24. data/lib/attachs/version.rb +1 -1
  25. data/lib/generators/attachs/install_generator.rb +13 -0
  26. data/lib/generators/attachs/templates/attachs.rb +2 -0
  27. data/lib/tasks/attachs.rake +30 -0
  28. data/test/attachment_test.rb +19 -0
  29. data/test/dummy/app/models/medium.rb +5 -0
  30. data/test/dummy/app/models/picture.rb +2 -0
  31. data/test/dummy/config/application.rb +1 -7
  32. data/test/dummy/config/initializers/attachs.rb +25 -0
  33. data/test/dummy/config/initializers/secret_token.rb +1 -0
  34. data/test/dummy/config/routes.rb +0 -1
  35. data/test/dummy/db/migrate/20140808012639_create_media.rb +11 -0
  36. data/test/dummy/db/schema.rb +14 -25
  37. data/test/dummy/log/development.log +9 -27
  38. data/test/dummy/log/test.log +52130 -11207
  39. data/test/dummy/test/fixtures/180x150.gif +0 -0
  40. data/test/generator_test.rb +18 -0
  41. data/test/local_storage_test.rb +57 -0
  42. data/test/magick_tool_test.rb +42 -0
  43. data/test/migration_test.rb +65 -0
  44. data/test/s3_storage_tes.rb +56 -0
  45. data/test/tasks_test.rb +61 -0
  46. data/test/test_helper.rb +17 -17
  47. data/test/validators_test.rb +100 -0
  48. metadata +85 -120
  49. data/README.rdoc +0 -104
  50. data/app/controllers/attachs/presets_controller.rb +0 -17
  51. data/config/routes.rb +0 -5
  52. data/lib/attachs/engine.rb +0 -7
  53. data/lib/attachs/magick/image.rb +0 -85
  54. data/lib/attachs/types/file.rb +0 -131
  55. data/lib/attachs/types/image.rb +0 -58
  56. data/lib/attachs/validators/attachment_content_type_validator.rb +0 -11
  57. data/lib/attachs/validators/attachment_presence_validator.rb +0 -9
  58. data/lib/attachs/validators/attachment_size_validator.rb +0 -21
  59. data/lib/attachs/validators/base.rb +0 -13
  60. data/lib/generators/attachs/s3/config_generator.rb +0 -13
  61. data/lib/generators/attachs/s3/templates/s3.yml +0 -16
  62. data/lib/tasks/attachs_tasks.rake +0 -71
  63. data/test/dummy/app/models/all_attached.rb +0 -16
  64. data/test/dummy/app/models/file_attached.rb +0 -3
  65. data/test/dummy/app/models/image_attached.rb +0 -3
  66. data/test/dummy/app/models/private_file_attached.rb +0 -3
  67. data/test/dummy/app/models/private_image_attached.rb +0 -3
  68. data/test/dummy/db/migrate/20130820222342_create_file_attacheds.rb +0 -9
  69. data/test/dummy/db/migrate/20130820222355_create_image_attacheds.rb +0 -9
  70. data/test/dummy/db/migrate/20130820222534_create_all_attacheds.rb +0 -19
  71. data/test/dummy/public/uploads/files/file.txt +0 -1
  72. data/test/dummy/public/uploads/images/big/image.jpg +0 -0
  73. data/test/dummy/public/uploads/images/original/image.jpg +0 -0
  74. data/test/dummy/public/uploads/images/small/image.jpg +0 -0
  75. data/test/dummy/test/fixtures/file_big.txt +0 -1
  76. data/test/dummy/test/fixtures/file_empty.txt +0 -1
  77. data/test/dummy/test/fixtures/image.jpg +0 -0
  78. data/test/dummy/test/fixtures/image_big.jpg +0 -0
  79. data/test/dummy/test/fixtures/image_empty.jpg +0 -0
  80. data/test/dummy/tmp/uploads/files/13898788260004868.txt +0 -1
  81. data/test/local_file_record_test.rb +0 -31
  82. data/test/local_file_string_test.rb +0 -23
  83. data/test/local_file_upload_test.rb +0 -30
  84. data/test/local_generate_test.rb +0 -25
  85. data/test/local_image_record_test.rb +0 -25
  86. data/test/local_image_string_test.rb +0 -34
  87. data/test/local_image_upload_test.rb +0 -27
  88. data/test/local_records_test.rb +0 -52
  89. data/test/local_validators_test.rb +0 -71
  90. data/test/s3_file_record_tes.rb +0 -21
  91. data/test/s3_file_string_tes.rb +0 -24
  92. data/test/s3_file_upload_tes.rb +0 -31
  93. data/test/s3_image_record_tes.rb +0 -26
  94. data/test/s3_image_string_tes.rb +0 -31
  95. data/test/s3_image_upload_tes.rb +0 -28
  96. data/test/s3_records_tes.rb +0 -56
  97. data/test/s3_validators_tes.rb +0 -72
@@ -0,0 +1,40 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ COLUMNS = {
5
+ filename: 'string',
6
+ content_type: 'string',
7
+ size: 'integer',
8
+ updated_at: 'datetime'
9
+ }
10
+
11
+ module AbstractAdapter
12
+ def add_attachment(table_name, *attachment_names)
13
+ attachment_names.each do |attachment_name|
14
+ COLUMNS.each do |column_name, column_type|
15
+ add_column table_name, "#{attachment_name}_#{column_name}", column_type
16
+ end
17
+ end
18
+ end
19
+
20
+ def remove_attachment(table_name, *attachment_names)
21
+ attachment_names.each do |attachment_name|
22
+ COLUMNS.each do |column_name, column_type|
23
+ remove_column table_name, "#{attachment_name}_#{column_name}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ module TableDefinition
30
+ def attachment(*attachment_names)
31
+ attachment_names.each do |attachment_name|
32
+ COLUMNS.each do |column_name, column_type|
33
+ column "#{attachment_name}_#{column_name}", column_type
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Migration
4
+ module CommandRecorder
5
+ def add_attachment(*args)
6
+ record :add_attachment, args
7
+ end
8
+
9
+ private
10
+
11
+ def invert_add_attachment(*args)
12
+ [:remove_attachment, args]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Validators
4
+ extend ActiveSupport::Concern
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Validators
4
+ class AttachmentContentTypeValidator < ActiveModel::EachValidator
5
+
6
+ def validate_each(record, attribute, value)
7
+ if value.exists?
8
+ if options.has_key? :with
9
+ if options[:with] !~ value.content_type
10
+ record.errors.add attribute, :attachment_content_type_with
11
+ end
12
+ elsif options.has_key? :in
13
+ if options[:in].exclude? value.content_type
14
+ record.errors.add attribute, :attachment_content_type_in, types: options[:in].to_sentence
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ module ClassMethods
22
+
23
+ def validates_attachment_content_type_of(*attr_names)
24
+ validates_with AttachmentContentTypeValidator, _merge_attributes(attr_names)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Validators
4
+ class AttachmentPresenceValidator < ActiveModel::EachValidator
5
+
6
+ def validate_each(record, attribute, value)
7
+ if value.blank?
8
+ record.errors.add attribute, :attachment_presence
9
+ end
10
+ end
11
+
12
+ end
13
+ module ClassMethods
14
+
15
+ def validates_attachment_presence_of(*attr_names)
16
+ validates_with AttachmentPresenceValidator, _merge_attributes(attr_names)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Validators
4
+ class AttachmentSizeValidator < ActiveModel::EachValidator
5
+ include ActionView::Helpers::NumberHelper
6
+
7
+ def validate_each(record, attribute, value)
8
+ if value.exists?
9
+ if options.has_key? :in
10
+ if options[:in].exclude? value.size
11
+ record.errors.add(
12
+ attribute,
13
+ :attachment_size_in,
14
+ min: number_to_human_size(options[:in].begin),
15
+ max: number_to_human_size(options[:in].end)
16
+ )
17
+ end
18
+ else
19
+ if options.has_key? :less_than and value.size > options[:less_than]
20
+ record.errors.add attribute, :attachment_size_less_than, count: number_to_human_size(options[:less_than])
21
+ end
22
+ if options.has_key? :greater_than and value.size < options[:greater_than]
23
+ record.errors.add attribute, :attachment_size_greater_than, count: number_to_human_size(options[:greater_than])
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ module ClassMethods
31
+
32
+ def validates_attachment_size_of(*attr_names)
33
+ validates_with AttachmentSizeValidator, _merge_attributes(attr_names)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,114 @@
1
+ module Attachs
2
+ class Attachment
3
+
4
+ attr_reader :record, :attribute, :filename, :content_type, :size, :updated_at, :upload, :options
5
+
6
+ delegate :basename, :extension, :image?, :url, :process, :destroy, :update, to: :type
7
+
8
+ def initialize(record, attribute, options, source=false)
9
+ @record = record
10
+ @attribute = attribute
11
+ @options = options
12
+ case source
13
+ when nil
14
+ %w(filename content_type size updated_at).each do |name|
15
+ record.send "#{attribute}_#{name}=", nil
16
+ end
17
+ when false
18
+ %w(filename content_type size updated_at).each do |name|
19
+ instance_variable_set :"@#{name}", record.send("#{attribute}_#{name}")
20
+ end
21
+ else
22
+ load source
23
+ %w(filename content_type size updated_at).each do |name|
24
+ record.send "#{attribute}_#{name}=", send(name)
25
+ end
26
+ end
27
+ end
28
+
29
+ def uploaded!
30
+ @upload = nil
31
+ end
32
+
33
+ def styles
34
+ @styles ||= ((options[:styles] || []) | Attachs.config.global_styles)
35
+ end
36
+
37
+ def private?
38
+ @private ||= options[:private] == true
39
+ end
40
+
41
+ def public?
42
+ !private?
43
+ end
44
+
45
+ def upload?
46
+ !upload.nil?
47
+ end
48
+
49
+ def processed?
50
+ exists? and record.persisted? and !(
51
+ record.send("#{attribute}_filename_changed?") or
52
+ record.send("#{attribute}_content_type_changed?") or
53
+ record.send("#{attribute}_size_changed?") or
54
+ record.send("#{attribute}_updated_at_changed?")
55
+ )
56
+ end
57
+
58
+ def exists?
59
+ filename and content_type and size and updated_at
60
+ end
61
+
62
+ def blank?
63
+ !exists?
64
+ end
65
+
66
+ def default?
67
+ !options[:default_path].nil?
68
+ end
69
+
70
+ def url?
71
+ public? and (default? or processed?)
72
+ end
73
+
74
+ protected
75
+
76
+ def load(source)
77
+ if source.is_a? URI
78
+ download = Tempfile.new('external')
79
+ File.open(download.path, 'wb') do |file|
80
+ Net::HTTP.start(source.host) do |http|
81
+ file.write http.get(source.path).body
82
+ end
83
+ end
84
+ type = `file --mime-type -b '#{download.path}'`.strip
85
+ source = ActionDispatch::Http::UploadedFile.new(
86
+ tempfile: download,
87
+ filename: File.basename(source.path),
88
+ type: type
89
+ )
90
+ end
91
+ if source.is_a? Attachs::Attachment
92
+ @filename = source.filename
93
+ @updated_at = source.updated_at
94
+ else
95
+ @upload = source
96
+ @filename = source.original_filename.downcase
97
+ @updated_at = Time.zone.now
98
+ end
99
+ @content_type = source.content_type
100
+ @size = source.size
101
+ end
102
+
103
+ def type
104
+ @type ||= begin
105
+ if exists?
106
+ Attachs::Types::Regular.new(self)
107
+ else
108
+ Attachs::Types::Default.new(self)
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+ end
@@ -2,7 +2,8 @@ en:
2
2
  errors:
3
3
  messages:
4
4
  attachment_presence: "can't be blank"
5
- attachment_content_type: "the allowed types are: %{types}"
5
+ attachment_content_type_with: "has not allowed type"
6
+ attachment_content_type_in: "must be of one of the following types: %{types}"
6
7
  attachment_size_less_than: "size must be less than %{count}"
7
8
  attachment_size_greater_than: "size must be greater than %{count}"
8
9
  attachment_size_in: "size must be between %{min} and %{max}"
@@ -2,7 +2,8 @@ es:
2
2
  errors:
3
3
  messages:
4
4
  attachment_presence: "no puede estar vacío"
5
- attachment_content_type: "los formatos permitidos son: %{types}"
5
+ attachment_content_type_with: "tiene un formato no permitido"
6
+ attachment_content_type_in: "debe ser de uno de los siguientes formatos: %{types}"
6
7
  attachment_size_less_than: "debe pesar menos de %{count}"
7
8
  attachment_size_greater_than: "debe pesar más de %{count}"
8
9
  attachment_size_in: "debe pesar entre %{min} y %{max}"
@@ -1,18 +1,38 @@
1
1
  module Attachs
2
2
  class Railtie < Rails::Railtie
3
3
 
4
- config.attachs = ActiveSupport::OrderedOptions.new
5
- config.attachs.presets = {}
6
- config.attachs.default_presets = []
7
- config.attachs.default_validations = {}
8
- config.attachs.base_url = ''
9
- config.attachs.storage = :local
10
-
11
4
  initializer 'attachs' do
12
- ::ActiveRecord::Base.send :include, Attachs::ActiveRecord::Base
13
- if config.attachs.storage == :s3
14
- require 'aws-sdk'
15
- Attachs::Storages::S3.config = YAML.load_file(Rails.root.join('config', 's3.yml'))
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
21
+ )
22
+ I18n.load_path += Dir[File.expand_path('../locales/*.yml', __FILE__)]
23
+ end
24
+
25
+ rake_tasks do
26
+ load 'tasks/attachs.rake'
27
+ end
28
+
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
+ )
16
36
  end
17
37
  end
18
38
 
@@ -0,0 +1,68 @@
1
+ module Attachs
2
+ module Storages
3
+ class Base
4
+
5
+ def initialize(attachment)
6
+ @attachment = attachment
7
+ end
8
+
9
+ protected
10
+
11
+ attr_reader :attachment
12
+
13
+ def resize(*args)
14
+ Attachs::Tools::Magick.resize(*args)
15
+ end
16
+
17
+ def template
18
+ @template = begin
19
+ if attachment.exists?
20
+ (attachment.options[:path] || Attachs.config.default_path).dup
21
+ else
22
+ attachment.options[:default_path].dup
23
+ end.tap do |path|
24
+ path.scan(/:([a-zA-Z0-9_]+)/).flatten.uniq.map(&:to_sym).each do |name|
25
+ if name != :style
26
+ path.gsub! ":#{name}", interpolate(name)
27
+ end
28
+ end
29
+ path.squeeze! '/'
30
+ path.slice! 0 if path[0] == '/'
31
+ end
32
+ end
33
+ end
34
+
35
+ def path(style=:original)
36
+ template.gsub(':style', style.to_s)
37
+ end
38
+
39
+ def interpolate(name)
40
+ if interpolation = Attachs.config.interpolations[name]
41
+ interpolation.call(attachment).to_s.parameterize
42
+ else
43
+ case name
44
+ when :basename
45
+ attachment.basename.parameterize
46
+ when :filename
47
+ "#{attachment.basename.parameterize}.#{attachment.extension}"
48
+ when :size
49
+ attachment.size
50
+ when :extension
51
+ attachment.extension
52
+ when :type
53
+ attachment.content_type.split('/').first.parameterize
54
+ when :timestamp
55
+ (attachment.updated_at.to_f * 10000000000).to_i
56
+ when :class
57
+ attachment.record.class.name.parameterize
58
+ when :id
59
+ attachment.record.id
60
+ when :param
61
+ attachment.record.to_param
62
+ end.to_s
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -1,64 +1,88 @@
1
1
  module Attachs
2
2
  module Storages
3
- class Local
3
+ class Local < Base
4
4
 
5
- def initialize(default, private)
6
- @tmp = (Rails.env.test? and !default)
7
- @private = private
5
+ def url(style=:original)
6
+ if attachment.url?
7
+ base_url.join(path(style)).to_s
8
+ end
8
9
  end
9
10
 
10
- def exists?(path)
11
- ::File.exists? realpath(path)
11
+ def process(force=false)
12
+ if attachment.upload?
13
+ FileUtils.mkdir_p realpath.dirname
14
+ attachment.upload.rewind
15
+ File.open(realpath, 'wb') do |file|
16
+ while chunk = attachment.upload.read(16 * 1024)
17
+ file.write chunk
18
+ end
19
+ end
20
+ attachment.uploaded!
21
+ end
22
+ process_styles force
12
23
  end
13
24
 
14
- def size(path)
15
- ::File.size realpath(path)
16
- end
17
-
18
- def realpath(path)
19
- base_path.join path
25
+ def process_styles(force=false)
26
+ if attachment.image?
27
+ attachment.styles.each do |style|
28
+ if force == true
29
+ delete realpath(style)
30
+ end
31
+ unless File.exist? realpath(style)
32
+ FileUtils.mkdir_p realpath(style).dirname
33
+ resize realpath, style, realpath(style)
34
+ end
35
+ end
36
+ end
20
37
  end
21
38
 
22
- def url(path)
23
- ::File.join Rails.application.config.attachs.base_url, path unless private?
39
+ def destroy
40
+ delete realpath
41
+ destroy_styles
24
42
  end
25
43
 
26
- def store(upload, path)
27
- create_dir realpath(path)
28
- upload.rewind # Hack to avoid empty files
29
- ::File.open(realpath(path), 'wb') do |file|
30
- while chunk = upload.read(16 * 1024)
31
- file.write(chunk)
44
+ def destroy_styles
45
+ if attachment.image?
46
+ attachment.styles.each do |style|
47
+ delete realpath(style)
32
48
  end
33
49
  end
34
50
  end
35
-
36
- def delete(path)
37
- ::File.delete realpath(path)
38
- end
39
-
40
- def magick(source, output, upload)
41
- create_dir realpath(output)
42
- yield Attachs::Magick::Image.new(realpath(source), realpath(output))
43
- end
44
51
 
45
52
  protected
46
53
 
47
- def tmp?
48
- @tmp == true
54
+ def move(origin, destination)
55
+ FileUtils.mv base_path.join(origin), base_path.join(destination)
56
+ end
57
+
58
+ def delete(realpath)
59
+ if File.exist? realpath
60
+ File.delete realpath
61
+ end
49
62
  end
50
63
 
51
- def private?
52
- @private == true
64
+ def realpath(style=:original)
65
+ base_path.join(path(style))
53
66
  end
54
67
 
55
68
  def base_path
56
- Rails.root.join tmp? ? 'tmp' : private? ? 'private' : 'public'
69
+ @base_path ||= begin
70
+ if attachment.private?
71
+ Rails.root.join 'private'
72
+ else
73
+ Rails.root.join 'public'
74
+ end
75
+ end
57
76
  end
58
77
 
59
- def create_dir(path)
60
- dir = base_path.join('uploads', path).dirname
61
- FileUtils.mkdir_p dir unless ::File.directory? dir
78
+ def base_url
79
+ @base_url ||= begin
80
+ if Attachs.config.base_url.present?
81
+ Pathname.new Attachs.config.base_url
82
+ else
83
+ Pathname.new '/'
84
+ end
85
+ end
62
86
  end
63
87
 
64
88
  end