attachs 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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