attachs 0.3.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 (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