attachs 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +98 -0
  4. data/Rakefile +34 -0
  5. data/app/controllers/attachs/presets_controller.rb +17 -0
  6. data/config/locales/en.yml +8 -0
  7. data/config/locales/es.yml +8 -0
  8. data/config/routes.rb +5 -0
  9. data/lib/attachs.rb +16 -0
  10. data/lib/attachs/active_record/base.rb +145 -0
  11. data/lib/attachs/engine.rb +7 -0
  12. data/lib/attachs/magick/image.rb +85 -0
  13. data/lib/attachs/railtie.rb +20 -0
  14. data/lib/attachs/storages/local.rb +61 -0
  15. data/lib/attachs/storages/s3.rb +77 -0
  16. data/lib/attachs/types/file.rb +113 -0
  17. data/lib/attachs/types/image.rb +40 -0
  18. data/lib/attachs/validators/attachment_content_type_validator.rb +11 -0
  19. data/lib/attachs/validators/attachment_presence_validator.rb +9 -0
  20. data/lib/attachs/validators/attachment_size_validator.rb +27 -0
  21. data/lib/attachs/validators/base.rb +13 -0
  22. data/lib/attachs/version.rb +5 -0
  23. data/lib/generators/attachs/s3/config_generator.rb +13 -0
  24. data/lib/generators/attachs/s3/templates/s3.yml +16 -0
  25. data/lib/tasks/attachs_tasks.rake +71 -0
  26. data/test/dummy/README.rdoc +28 -0
  27. data/test/dummy/Rakefile +6 -0
  28. data/test/dummy/app/assets/javascripts/application.js +13 -0
  29. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  30. data/test/dummy/app/controllers/application_controller.rb +5 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/models/all_attached.rb +16 -0
  33. data/test/dummy/app/models/file_attached.rb +3 -0
  34. data/test/dummy/app/models/image_attached.rb +3 -0
  35. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  36. data/test/dummy/bin/bundle +3 -0
  37. data/test/dummy/bin/rails +4 -0
  38. data/test/dummy/bin/rake +4 -0
  39. data/test/dummy/config.ru +4 -0
  40. data/test/dummy/config/application.rb +29 -0
  41. data/test/dummy/config/boot.rb +5 -0
  42. data/test/dummy/config/database.yml +25 -0
  43. data/test/dummy/config/environment.rb +5 -0
  44. data/test/dummy/config/environments/development.rb +29 -0
  45. data/test/dummy/config/environments/production.rb +80 -0
  46. data/test/dummy/config/environments/test.rb +36 -0
  47. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  48. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  49. data/test/dummy/config/initializers/inflections.rb +16 -0
  50. data/test/dummy/config/initializers/mime_types.rb +5 -0
  51. data/test/dummy/config/initializers/secret_token.rb +12 -0
  52. data/test/dummy/config/initializers/session_store.rb +3 -0
  53. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/test/dummy/config/locales/en.yml +23 -0
  55. data/test/dummy/config/routes.rb +3 -0
  56. data/test/dummy/db/development.sqlite3 +0 -0
  57. data/test/dummy/db/migrate/20130820222342_create_file_attacheds.rb +9 -0
  58. data/test/dummy/db/migrate/20130820222355_create_image_attacheds.rb +9 -0
  59. data/test/dummy/db/migrate/20130820222534_create_all_attacheds.rb +19 -0
  60. data/test/dummy/db/schema.rb +44 -0
  61. data/test/dummy/db/test.sqlite3 +0 -0
  62. data/test/dummy/log/development.log +28 -0
  63. data/test/dummy/log/test.log +8545 -0
  64. data/test/dummy/public/404.html +58 -0
  65. data/test/dummy/public/422.html +58 -0
  66. data/test/dummy/public/500.html +57 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/dummy/public/uploads/files/file.txt +1 -0
  69. data/test/dummy/public/uploads/images/big/image.jpg +0 -0
  70. data/test/dummy/public/uploads/images/original/image.jpg +0 -0
  71. data/test/dummy/public/uploads/images/small/image.jpg +0 -0
  72. data/test/dummy/test/fixtures/file.txt +1 -0
  73. data/test/dummy/test/fixtures/file_big.txt +1 -0
  74. data/test/dummy/test/fixtures/file_empty.txt +1 -0
  75. data/test/dummy/test/fixtures/image.jpg +0 -0
  76. data/test/dummy/test/fixtures/image_big.jpg +0 -0
  77. data/test/dummy/test/fixtures/image_empty.jpg +0 -0
  78. data/test/local_file_record_test.rb +20 -0
  79. data/test/local_file_string_test.rb +23 -0
  80. data/test/local_file_upload_test.rb +30 -0
  81. data/test/local_generate_test.rb +25 -0
  82. data/test/local_image_record_test.rb +25 -0
  83. data/test/local_image_string_test.rb +34 -0
  84. data/test/local_image_upload_test.rb +27 -0
  85. data/test/local_records_test.rb +52 -0
  86. data/test/local_validators_test.rb +71 -0
  87. data/test/s3_file_record_tes.rb +21 -0
  88. data/test/s3_file_string_tes.rb +24 -0
  89. data/test/s3_file_upload_tes.rb +31 -0
  90. data/test/s3_image_record_tes.rb +26 -0
  91. data/test/s3_image_string_tes.rb +31 -0
  92. data/test/s3_image_upload_tes.rb +28 -0
  93. data/test/s3_records_tes.rb +56 -0
  94. data/test/s3_validators_tes.rb +72 -0
  95. data/test/test_helper.rb +49 -0
  96. metadata +250 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54ae49b0a6e0cb9b5e170ac1b1559dd6e614055b
4
+ data.tar.gz: 273fcb21962f1b648de5d86064775d9b9b0dfddb
5
+ SHA512:
6
+ metadata.gz: 2512efff14fad943789edf5867e952c558fed32fb25d31f4fde483015784d1cd2d5603075a2e33109b1b0fd8b3fce7ad8a74f0a2291265d60306f1cdadc8f6cd
7
+ data.tar.gz: c089f8e848fd436b8491144bf3a0ef57c00be075145cd8f495e6834faaf9333a1774efb37d621da9639da77dac5265a7572e0a7c417a340a69e5aeb542e9ab6b
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Mattways
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ {<img src="https://codeclimate.com/github/mattways/attachs.png" />}[https://codeclimate.com/github/mattways/attachs] {<img src="https://travis-ci.org/mattways/attachs.png?branch=master" alt="Build Status" />}[https://travis-ci.org/mattways/attachs] {<img src="https://gemnasium.com/mattways/attachs.png" alt="Dependency Status" />}[https://gemnasium.com/mattways/attachs]
2
+
3
+ = Attachs
4
+
5
+ Minimalistic toolkit to attach file and images to records.
6
+
7
+ = Install
8
+
9
+ Put this line in your Gemfile:
10
+ gem 'attachs'
11
+
12
+ Then bundle:
13
+ $ bundle
14
+
15
+ ImageMagick must be install, you can install it with homebrew:
16
+ brew install imagemagick
17
+
18
+ = Usage
19
+
20
+ Mount the engine at the end of you routes.rb (only if you are using local storage):
21
+ mount Attachs:Engine: '/' # Will be use to generate on the fly missing image presets
22
+
23
+ Add the column to your table (just a string):
24
+ create_table :models do |t|
25
+ t.string :attr
26
+ end
27
+
28
+ If you need a file:
29
+ class Model < ActiveRecord::Base
30
+ has_attached_file :attr, default: 'file.txt'
31
+ end
32
+
33
+ If you need a image:
34
+ class Model < ActiveRecord::Base
35
+ has_attached_image :attr, presets: [:small, :big], default: 'assets/image.jpg'
36
+ end
37
+
38
+ Define presets in your application.rb
39
+ config.attachs.presets = {
40
+ big: { method: :fit, width: 1024, height: 768 }, # Fit will scale the image until it fit in the space without cropping
41
+ small: { method: :fill, width: 120, height: :120 }, # Fill will scale the image to fill all the space and then crop
42
+ custom: proc { |image| image.convert resize: '100x100' } # ImageMagick wrapper to do whatever you want
43
+ }
44
+ config.attachs.default_presets = [:small] # Define the default presets for all models with attached images
45
+ config.attachs.storage = :local # The default it's local, you can use :s3 as well
46
+
47
+ If you want to use S3 create a s3.yml config file with this generator and complete it:
48
+ rails g attachs:s3:config
49
+
50
+ The validation works very similar to paperclip:
51
+ class Model < ActiveRecord::Base
52
+ has_attached_file :attr
53
+ validates :attr, attachment_presence: true, attachment_size: { in: 0..4.megabytes }, attachment_content_type: { in: ['txt'] }
54
+ end
55
+
56
+ If you want to translate the errores the keys are:
57
+ errors.messages.attachment_presence
58
+ errors.messages.attachment_size_in # :max and :min
59
+ errors.messages.attachment_size_less_than # :count
60
+ errors.messages.attachment_size_greater_than # :count
61
+ errors.messages.attachment_content_type # :types
62
+
63
+ In your views:
64
+ a{ href: record.file.url } # To get the file url
65
+ a{ href: record.image.url } # To get the original image
66
+ a{ href: record.image.url(:big) } # To get the a thumb
67
+
68
+ In your forms:
69
+ = f.file_field :attr
70
+
71
+ If your file it's optional:
72
+ = f.check_box :delete_attr
73
+
74
+ = FAQ
75
+
76
+ == How can I use a cdn with this plugin?
77
+
78
+ Define a base url in your application.rb:
79
+ config.attachs.base_url = 'http://cdn.example.com'
80
+
81
+ == How can I automatically create buckets?
82
+
83
+ Use this rake task after create the s3.yml file:
84
+ rake attachs:s3:buckets:create
85
+
86
+ == How can I clean a preset?
87
+
88
+ Use this rake task:
89
+ rake attachs:presets:clean MODEL=models PRESETS=first_preset,second_preset
90
+
91
+ == How can I refresh a preset?
92
+
93
+ Use this rake task:
94
+ rake attachs:presets:refresh MODEL=models PRESETS=first_preset,second_preset
95
+
96
+ == How can I migrate from versions before 0.3.0
97
+
98
+ All will work the same except from the name of the config parameter, replace uploads with attachs.
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Attachs'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,17 @@
1
+ module Attachs
2
+ class PresetsController < ActionController::Base
3
+
4
+ def generate
5
+ filename = "#{params[:image]}.#{params[:format]}"
6
+ preset = params[:preset].gsub('-', '_').to_sym
7
+ if Rails.application.config.attachs.presets.has_key? preset
8
+ image = Attachs::Types::Image.new(filename)
9
+ if image.exists? and !image.exists?(preset)
10
+ image.generate_preset preset
11
+ redirect_to image.url(params[:preset]), cb: Random.rand(100000)
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ attachment_presence: "can't be blank"
5
+ attachment_content_type: "the allowed types are: %{types}"
6
+ attachment_size_less_than: "size must be less than %{count}"
7
+ attachment_size_greater_than: "size must be greater than %{count}"
8
+ attachment_size_in: "size must be between %{min} and %{max}"
@@ -0,0 +1,8 @@
1
+ es:
2
+ errors:
3
+ messages:
4
+ attachment_presence: "no puede estar vacío"
5
+ attachment_content_type: "los formatos permitidos son: %{types}"
6
+ attachment_size_less_than: "debe pesar menos de %{count}"
7
+ attachment_size_greater_than: "debe pesar más de %{count}"
8
+ attachment_size_in: "debe pesar entre %{min} y %{max}"
@@ -0,0 +1,5 @@
1
+ Attachs::Engine.routes.draw do
2
+
3
+ get 'uploads/images/:preset/:image.:format', to: 'presets#generate', status: 404
4
+
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'attachs/active_record/base'
2
+ require 'attachs/magick/image'
3
+ require 'attachs/storages/local'
4
+ require 'attachs/storages/s3'
5
+ require 'attachs/types/file'
6
+ require 'attachs/types/image'
7
+ require 'attachs/validators/base'
8
+ require 'attachs/validators/attachment_content_type_validator'
9
+ require 'attachs/validators/attachment_presence_validator'
10
+ require 'attachs/validators/attachment_size_validator'
11
+ require 'attachs/engine'
12
+ require 'attachs/railtie'
13
+ require 'attachs/version'
14
+
15
+ module Attachs
16
+ end
@@ -0,0 +1,145 @@
1
+ module Attachs
2
+ module ActiveRecord
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ singleton_class.class_eval do
8
+ [:file, :image].each do |type|
9
+ name = :"has_attached_#{type}"
10
+ define_method name do |*args|
11
+ options = args.extract_options!
12
+ options[:type] = type
13
+ define_attachment *args.append(options)
14
+ end
15
+ alias_method :"attached_#{type}", name
16
+ end
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ attr_reader :stored_attachments, :deleted_attachments
23
+
24
+ def attachments
25
+ @attachments ||= {}
26
+ end
27
+
28
+ def build_attachment_instance(source, options)
29
+ klass = options.has_key?(:type) ? options[:type].to_s.classify : 'File'
30
+ Attachs::Types.const_get(klass).new source, options
31
+ end
32
+
33
+ def check_changed_attachments
34
+ @stored_attachments = []
35
+ @deleted_attachments = []
36
+ self.class.attachments.each do |attr, options|
37
+ if changed_attributes.has_key? attr.to_s
38
+ stored = attributes[attr.to_s]
39
+ deleted = changed_attributes[attr.to_s]
40
+ add_changed_attachment stored, options, :stored if stored.present?
41
+ add_changed_attachment deleted, options, :deleted if deleted.present?
42
+ end
43
+ end
44
+ end
45
+
46
+ def iterate_attachments
47
+ self.class.attachments.each do |attr, options|
48
+ next unless instance = send(attr)
49
+ yield instance
50
+ end
51
+ end
52
+
53
+ def add_changed_attachment(source, options, type)
54
+ (type == :stored ? stored_attachments : deleted_attachments) << (source.is_a?(String) ? build_attachment_instance(source, options) : source)
55
+ end
56
+
57
+ def store_attachments
58
+ iterate_attachments { |a| a.store }
59
+ end
60
+
61
+ def delete_attachments
62
+ iterate_attachments { |a| a.delete }
63
+ end
64
+
65
+ def remove_stored_attachments
66
+ stored_attachments.each { |a| a.delete }
67
+ end
68
+
69
+ def remove_deleted_attachments
70
+ deleted_attachments.each { |a| a.delete }
71
+ end
72
+
73
+ module ClassMethods
74
+
75
+ attr_reader :attachments
76
+
77
+ def inherited(subclass)
78
+ subclass.instance_variable_set(:@attachments, @attachments)
79
+ super
80
+ end
81
+
82
+ def attachable?
83
+ attachments.present?
84
+ end
85
+
86
+ protected
87
+
88
+ def define_attachment(*args)
89
+ options = args.extract_options!
90
+ make_attachable unless attachable?
91
+ args.each do |attr|
92
+ define_attachable_attribute_methods attr.to_sym, options
93
+ define_default_validations attr.to_sym, options
94
+ attachments[attr.to_sym] = options
95
+ end
96
+ end
97
+
98
+ def make_attachable
99
+ before_save :store_attachments, :check_changed_attachments
100
+ after_save :remove_deleted_attachments
101
+ before_destroy :delete_attachments
102
+ after_rollback :remove_stored_attachments
103
+ @attachments = {}
104
+ end
105
+
106
+ def define_default_validations(attr, options)
107
+ default_validations = Rails.application.config.attachs.default_validations[options[:type]]
108
+ validates attr, default_validations.dup if default_validations.present?
109
+ end
110
+
111
+ def define_attachable_attribute_methods(attr, options)
112
+ ['set','get'].each { |method| send "define_attachable_attribute_method_#{method}", attr, options }
113
+ end
114
+
115
+ def define_attachable_attribute_method_set(attr, options)
116
+ define_method "#{attr}=" do |value|
117
+ if value.is_a? ActionDispatch::Http::UploadedFile or value.is_a? Rack::Test::UploadedFile or (value.is_a? String and value =~ /^[a-zA-Z0-9_-]+\.[a-zA-z]+$/)
118
+ attachment = build_attachment_instance(value, options)
119
+ attachments[attr] = attachment
120
+ write_attribute attr, attachment.filename
121
+ end
122
+ end
123
+ define_method "delete_#{attr}=" do |value|
124
+ if attribute_present? attr and value == '1'
125
+ send "#{attr}_will_change!"
126
+ attachments[attr] = nil
127
+ attributes[attr.to_s] = nil
128
+ end
129
+ instance_variable_set("@delete_#{attr}".to_sym, value)
130
+ end
131
+ end
132
+
133
+ def define_attachable_attribute_method_get(attr, options)
134
+ define_method attr do
135
+ return attachments[attr] if attachments.has_key? attr
136
+ return nil if super().nil? and not options.has_key? :default
137
+ attachments[attr] = build_attachment_instance(super(), options)
138
+ end
139
+ attr_reader :"delete_#{attr}"
140
+ end
141
+
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,7 @@
1
+ module Attachs
2
+ class Engine < Rails::Engine
3
+
4
+ isolate_namespace Attachs
5
+
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ module Attachs
2
+ module Magick
3
+ class Image
4
+
5
+ def initialize(source, output=nil)
6
+ @source = source
7
+ @output = output
8
+ end
9
+
10
+ def convert(args)
11
+ tokens = [convert? ? 'convert' : 'mogrify']
12
+ file_to_tokens tokens
13
+ args_to_tokens args, tokens
14
+ tokens << "\"#{output}\"" if convert?
15
+ success, output = run(tokens)
16
+ if block_given?
17
+ yield success, output
18
+ else
19
+ success
20
+ end
21
+ end
22
+
23
+ def identify(args)
24
+ tokens = ['identify']
25
+ args_to_tokens args, tokens
26
+ file_to_tokens tokens
27
+ success, output = run(tokens)
28
+ if block_given?
29
+ yield success, output
30
+ else
31
+ success
32
+ end
33
+ end
34
+
35
+ def dimensions
36
+ identify(format: '%wx%h') do |success, output|
37
+ success ? output.chomp.split('x').map(&:to_i) : []
38
+ end
39
+ end
40
+
41
+ def width
42
+ dimensions[0]
43
+ end
44
+
45
+ def height
46
+ dimensions[1]
47
+ end
48
+
49
+ def resize_to_fill(max_width, max_height)
50
+ width, height = dimensions
51
+ scale = [max_width.to_f/width, max_height.to_f/height].max
52
+ convert resize: "#{(scale*width).ceil}x#{(scale*height).ceil}", gravity: 'center', crop: "#{max_width}x#{max_height}+0+0"
53
+ end
54
+
55
+ def resize_to_fit(max_width, max_height)
56
+ width, height = dimensions
57
+ scale = [max_width.to_f/width, max_height.to_f/height].reject{ |s| s==0 }.min
58
+ convert resize: "#{(scale*width).ceil}x#{(scale*height).ceil}", gravity: 'center'
59
+ end
60
+
61
+ protected
62
+
63
+ attr_reader :source, :output
64
+
65
+ def convert?
66
+ !(output.nil? and ::File.exists?(output))
67
+ end
68
+
69
+ def file_to_tokens(tokens)
70
+ tokens << (convert? ? "\"#{source}\"" : output)
71
+ end
72
+
73
+ def args_to_tokens(args, tokens)
74
+ args.each { |k, v| tokens << "-#{k} #{v}" }
75
+ end
76
+
77
+ def run(tokens)
78
+ output = `#{tokens.join(' ')}`
79
+ success = $?.exitstatus == 0
80
+ [success, output]
81
+ end
82
+
83
+ end
84
+ end
85
+ end