attachy 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +21 -0
- data/README.md +108 -0
- data/lib/assets/javascripts/attachy.js +289 -0
- data/lib/attachy.rb +13 -0
- data/lib/attachy/builders/attachy/form_builder.rb +15 -0
- data/lib/attachy/engine.rb +16 -0
- data/lib/attachy/helpers/attachy/view_helper.rb +31 -0
- data/lib/attachy/models/attachy/extension.rb +79 -0
- data/lib/attachy/models/attachy/file.rb +59 -0
- data/lib/attachy/models/attachy/viewer.rb +170 -0
- data/lib/attachy/version.rb +3 -0
- data/lib/generators/attachy/install_generator.rb +25 -0
- data/lib/generators/attachy/templates/config/attachy.yml.erb +15 -0
- data/lib/generators/attachy/templates/db/migrate/create_attachy_files_table.rb +19 -0
- data/lib/generators/attachy/templates/public/cloudinary_cors.html +46 -0
- data/spec/builders/attachy/form_builder/attachy_content_spec.rb +35 -0
- data/spec/builders/attachy/form_builder/attachy_file_field_spec.rb +23 -0
- data/spec/builders/attachy/form_builder/attachy_spec.rb +35 -0
- data/spec/factories/attachy/file.rb +12 -0
- data/spec/factories/user.rb +5 -0
- data/spec/helpers/attachy/attachy_content_spec.rb +21 -0
- data/spec/helpers/attachy/attachy_file_field_spec.rb +21 -0
- data/spec/helpers/attachy/attachy_spec.rb +35 -0
- data/spec/models/attachy/callback/destroy_file_spec.rb +55 -0
- data/spec/models/attachy/callback/remove_tmp_tag_spec.rb +11 -0
- data/spec/models/attachy/extension/user/avatar_spec.rb +91 -0
- data/spec/models/attachy/extension/user/photos_spec.rb +88 -0
- data/spec/models/attachy/file/config_spec.rb +11 -0
- data/spec/models/attachy/file/default_spec.rb +26 -0
- data/spec/models/attachy/file/path_spec.rb +16 -0
- data/spec/models/attachy/file/transform_spec.rb +86 -0
- data/spec/models/attachy/file_spec.rb +14 -0
- data/spec/models/attachy/viewer/attachments_spec.rb +28 -0
- data/spec/models/attachy/viewer/button_label_options_spec.rb +15 -0
- data/spec/models/attachy/viewer/button_label_spec.rb +34 -0
- data/spec/models/attachy/viewer/content_options_spec.rb +29 -0
- data/spec/models/attachy/viewer/content_spec.rb +62 -0
- data/spec/models/attachy/viewer/field_options_spec.rb +18 -0
- data/spec/models/attachy/viewer/field_spec.rb +56 -0
- data/spec/models/attachy/viewer/file_button_options_spec.rb +18 -0
- data/spec/models/attachy/viewer/file_button_spec.rb +57 -0
- data/spec/models/attachy/viewer/file_field_options_spec.rb +90 -0
- data/spec/models/attachy/viewer/file_field_spec.rb +25 -0
- data/spec/models/attachy/viewer/hidden_field_spec.rb +22 -0
- data/spec/models/attachy/viewer/image_spec.rb +170 -0
- data/spec/models/attachy/viewer/link_options_spec.rb +18 -0
- data/spec/models/attachy/viewer/link_spec.rb +131 -0
- data/spec/models/attachy/viewer/node_options_spec.rb +18 -0
- data/spec/models/attachy/viewer/node_spec.rb +134 -0
- data/spec/models/attachy/viewer/nodes_spec.rb +21 -0
- data/spec/models/attachy/viewer/remove_button_options_spec.rb +18 -0
- data/spec/models/attachy/viewer/transform_spec.rb +44 -0
- data/spec/models/attachy/viewer/value_spec.rb +83 -0
- data/spec/models/user_spec.rb +9 -0
- data/spec/rails_helper.rb +11 -0
- data/spec/support/common.rb +20 -0
- data/spec/support/database_cleaner.rb +19 -0
- data/spec/support/db/migrate/create_users_table.rb +7 -0
- data/spec/support/factory_girl.rb +7 -0
- data/spec/support/html_matchers.rb +5 -0
- data/spec/support/migrate.rb +4 -0
- data/spec/support/models/user.rb +5 -0
- data/spec/support/shoulda.rb +8 -0
- metadata +365 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Attachy
|
|
2
|
+
module FormBuilder
|
|
3
|
+
def attachy(method, options = {}, &block)
|
|
4
|
+
@template.attachy method, object, options, block
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def attachy_content(method, options = {}, &block)
|
|
8
|
+
@template.attachy_content method, object, options, block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def attachy_file_field(method, options = {})
|
|
12
|
+
@template.attachy_file_field method, object, options
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'attachy/builders/attachy/form_builder'
|
|
2
|
+
require 'attachy/helpers/attachy/view_helper'
|
|
3
|
+
|
|
4
|
+
module Attachy
|
|
5
|
+
module Rails
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
initializer 'attachy.include_view_helper' do |_app|
|
|
8
|
+
ActiveSupport.on_load :action_view do
|
|
9
|
+
include ViewHelper
|
|
10
|
+
|
|
11
|
+
ActionView::Helpers::FormBuilder.include FormBuilder
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Attachy
|
|
2
|
+
module ViewHelper
|
|
3
|
+
def attachy(method, object, options, block)
|
|
4
|
+
viewer = Viewer.new(method, object, options, self)
|
|
5
|
+
|
|
6
|
+
return block.call(viewer) if block
|
|
7
|
+
|
|
8
|
+
viewer.field
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def attachy_content(method, object, options)
|
|
12
|
+
Viewer.new(method, object, options, self).content
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def attachy_file_field(method, object, options)
|
|
16
|
+
Viewer.new(method, object, options, self).file_field
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def attachy_image(method, object, options)
|
|
20
|
+
Viewer.new(method, object, options, self).image
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def attachy_link(method, object, options)
|
|
24
|
+
Viewer.new(method, object, options, self).link
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def attachy_node(method, object, options)
|
|
28
|
+
Viewer.new(method, object, options, self).node
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Attachy
|
|
2
|
+
module Extension
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def attachy_class
|
|
9
|
+
Attachy::File
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def attachies_for(data, scope)
|
|
13
|
+
json = JSON.parse(data, symbolize_names: true)
|
|
14
|
+
|
|
15
|
+
[json].flatten.map do |data|
|
|
16
|
+
if data[:id]
|
|
17
|
+
attachy_class.find data[:id]
|
|
18
|
+
else
|
|
19
|
+
attachy_class.new data.slice(*fields).merge(scope: scope)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fields
|
|
25
|
+
@fields ||= attachy_class.column_names.map(&:to_sym)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class_methods do
|
|
30
|
+
def has_attachment(scope, options = {})
|
|
31
|
+
define_attachy scope, options.merge(multiple: false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def has_attachments(scope, options = {})
|
|
35
|
+
define_attachy scope, options.merge(multiple: true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def define_attachy(scope, options)
|
|
41
|
+
association = "#{scope}_files"
|
|
42
|
+
|
|
43
|
+
has_many association.to_sym,
|
|
44
|
+
-> { where scope: scope },
|
|
45
|
+
as: :attachable,
|
|
46
|
+
class_name: Attachy::File,
|
|
47
|
+
dependent: :destroy
|
|
48
|
+
|
|
49
|
+
define_method scope do
|
|
50
|
+
value = send(association)
|
|
51
|
+
|
|
52
|
+
return value if options[:multiple]
|
|
53
|
+
|
|
54
|
+
return Attachy::File.default if value.blank?
|
|
55
|
+
|
|
56
|
+
value.last
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
define_method "#{scope}=" do |data|
|
|
60
|
+
attachies = attachies_for(data, scope)
|
|
61
|
+
|
|
62
|
+
if attachies.present?
|
|
63
|
+
send "#{association}=", attachies
|
|
64
|
+
else
|
|
65
|
+
send(association).destroy_all
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
define_method "#{scope}?" do
|
|
70
|
+
send("#{scope}_files").present?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
define_method "#{scope}_metadata" do
|
|
74
|
+
options.merge scope: scope
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Attachy
|
|
2
|
+
class File < ::ActiveRecord::Base
|
|
3
|
+
self.table_name = 'attachy_files'
|
|
4
|
+
|
|
5
|
+
before_destroy :destroy_file
|
|
6
|
+
|
|
7
|
+
after_create :remove_tmp_tag
|
|
8
|
+
|
|
9
|
+
belongs_to :attachable, polymorphic: true
|
|
10
|
+
|
|
11
|
+
validates :format, :height, :public_id, :resource_type, :scope, :version, :width, presence: true
|
|
12
|
+
|
|
13
|
+
def transform(options = {})
|
|
14
|
+
options = options.reverse_merge(
|
|
15
|
+
format: format,
|
|
16
|
+
public_id: public_id,
|
|
17
|
+
secure: true,
|
|
18
|
+
sign_url: true,
|
|
19
|
+
version: version
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if options[:crop] == :none
|
|
23
|
+
options.delete :crop
|
|
24
|
+
options.delete :height
|
|
25
|
+
options.delete :width
|
|
26
|
+
elsif options[:crop].blank?
|
|
27
|
+
options[:crop] = :fill
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def url(options = {})
|
|
34
|
+
Cloudinary::Utils.cloudinary_url public_id, transform(options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.config
|
|
38
|
+
::Rails.application.config_for :attachy
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.default
|
|
42
|
+
new config['default']['image']
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def destroy_file
|
|
48
|
+
Cloudinary::Uploader.destroy public_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def remove_tmp_tag
|
|
52
|
+
Cloudinary::Uploader.remove_tag TMP_TAG, [public_id]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def h
|
|
56
|
+
ActionController::Base.helpers
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
require 'mime/types'
|
|
2
|
+
|
|
3
|
+
module Attachy
|
|
4
|
+
class Viewer
|
|
5
|
+
def initialize(method, object, options = {}, view = ActionController::Base.helpers)
|
|
6
|
+
@method = method
|
|
7
|
+
@object = object
|
|
8
|
+
@options = options
|
|
9
|
+
@view = view
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def attachments
|
|
13
|
+
[criteria].flatten
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def button_label_options
|
|
17
|
+
{ text: '...' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def button_label(html: button_label_options)
|
|
21
|
+
html = htm(:button).merge(html)
|
|
22
|
+
|
|
23
|
+
@view.content_tag :span, html.delete(:text), html
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def content(html: {})
|
|
27
|
+
html = content_options.merge(html)
|
|
28
|
+
|
|
29
|
+
return yield(html, attachments) if block_given?
|
|
30
|
+
|
|
31
|
+
@view.content_tag :ul, nodes.join.html_safe, html
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def content_options
|
|
35
|
+
{
|
|
36
|
+
class: :attachy__content,
|
|
37
|
+
|
|
38
|
+
data: {
|
|
39
|
+
crop: metadata[:crop],
|
|
40
|
+
height: transform[:height],
|
|
41
|
+
multiple: metadata[:multiple],
|
|
42
|
+
width: transform[:width]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def field_options
|
|
48
|
+
{ class: :attachy }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def field(html: {})
|
|
52
|
+
html = field_options.merge(html)
|
|
53
|
+
|
|
54
|
+
return yield(html) if block_given?
|
|
55
|
+
|
|
56
|
+
@view.content_tag :div, content + file_button, html
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def file_button_options
|
|
60
|
+
{ class: :attachy__button }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def file_button(html: htm(:button))
|
|
64
|
+
html = file_button_options.merge(html)
|
|
65
|
+
|
|
66
|
+
return yield(html) if block_given?
|
|
67
|
+
|
|
68
|
+
@view.content_tag :div, button_label + file_field + hidden_field, html
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def file_field_options
|
|
72
|
+
options = { class: :attachy__fileupload }
|
|
73
|
+
accept = MIME::Types.type_for([metadata[:accept]].flatten.map(&:to_s))
|
|
74
|
+
|
|
75
|
+
options[:accept] = accept.join(',') if accept.present?
|
|
76
|
+
options[:multiple] = true if metadata[:multiple]
|
|
77
|
+
|
|
78
|
+
options
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def file_field(html: file_field_options)
|
|
82
|
+
options = { html: html, tags: [ENV_TAG, TMP_TAG] }
|
|
83
|
+
|
|
84
|
+
options[:folder] = metadata[:folder] if metadata[:folder]
|
|
85
|
+
|
|
86
|
+
@view.cl_image_upload_tag @method, options
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def hidden_field
|
|
90
|
+
@view.hidden_field @object.class.name.downcase, @method, value: value, id: nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def image(file = criteria, t: transform, html: htm)
|
|
94
|
+
url = file.url(t)
|
|
95
|
+
html = html.reverse_merge(height: t[:height], width: t[:width])
|
|
96
|
+
html[:data] = file.transform(t)
|
|
97
|
+
|
|
98
|
+
@view.image_tag url, html
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def link_options
|
|
102
|
+
{ class: :attachy__link }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def link(file = criteria, t: transform, tl: { crop: :none }, html: {})
|
|
106
|
+
html = html.reverse_merge(link_options.merge(data: tl))
|
|
107
|
+
|
|
108
|
+
return yield(html, attachments) if block_given?
|
|
109
|
+
|
|
110
|
+
@view.link_to image(file, t: t), file.url(tl), html
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def node_options
|
|
114
|
+
{ class: :attachy__node }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def node(file = criteria, tl: { crop: :none }, t: transform, html: {})
|
|
118
|
+
html = html.reverse_merge(node_options)
|
|
119
|
+
|
|
120
|
+
return yield(html, attachments) if block_given?
|
|
121
|
+
|
|
122
|
+
value = [link(file, t: t, tl: tl)]
|
|
123
|
+
value << remove_button unless @options[:destroy] == false
|
|
124
|
+
|
|
125
|
+
@view.content_tag :li, value.join.html_safe, html
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def nodes
|
|
129
|
+
attachments.map { |file| node file }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def remove_button_options
|
|
133
|
+
{ class: :attachy__remove }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def remove_button(html: {})
|
|
137
|
+
html = html.reverse_merge(remove_button_options)
|
|
138
|
+
|
|
139
|
+
return yield(html) if block_given?
|
|
140
|
+
|
|
141
|
+
@view.content_tag :span, '×'.html_safe, html
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def value
|
|
145
|
+
[(default? ? [] : criteria)].flatten.to_json
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def criteria
|
|
151
|
+
@criteria ||= @object.send(@method)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def default?
|
|
155
|
+
attachments.size == 1 && attachments.last.public_id == Attachy::File.default.public_id
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def htm(resource_type = :image)
|
|
159
|
+
@options.dig(resource_type, :html) || {}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def metadata
|
|
163
|
+
@metadata ||= @object.send("#{@method}_metadata")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def transform(path = [:t])
|
|
167
|
+
@options.dig(*path) || {}
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Attachy
|
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
4
|
+
|
|
5
|
+
argument :format , type: :string, default: :jpg
|
|
6
|
+
argument :public_id, type: :string, default: :default
|
|
7
|
+
argument :version , type: :string, default: 1
|
|
8
|
+
|
|
9
|
+
desc 'configure Attachy'
|
|
10
|
+
|
|
11
|
+
def create_config
|
|
12
|
+
template 'config/attachy.yml.erb', 'config/attachy.yml'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_cors
|
|
16
|
+
template 'public/cloudinary_cors.html', 'public/cloudinary_cors.html'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_migration
|
|
20
|
+
version = Time.zone.now.strftime('%Y%m%d%H%M%S')
|
|
21
|
+
|
|
22
|
+
template 'db/migrate/create_attachy_files_table.rb', "db/migrate/#{version}_create_attachy_files_table.rb"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateAttachyFilesTable < ActiveRecord::Migration[5.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :attachy_files do |t|
|
|
4
|
+
t.integer :height , null: false
|
|
5
|
+
t.integer :width , null: false
|
|
6
|
+
t.string :format , null: false
|
|
7
|
+
t.string :public_id , null: false
|
|
8
|
+
t.string :resource_type, null: false, default: :image
|
|
9
|
+
t.string :scope , null: false
|
|
10
|
+
t.string :version , null: false
|
|
11
|
+
|
|
12
|
+
t.references :attachable, polymorphic: true
|
|
13
|
+
|
|
14
|
+
t.timestamps null: false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_index :attachy_files, [:attachable_type, :attachable_id, :scope], name: :index_attachy_files_on_attachable_and_scope
|
|
18
|
+
end
|
|
19
|
+
end
|