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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +108 -0
  5. data/lib/assets/javascripts/attachy.js +289 -0
  6. data/lib/attachy.rb +13 -0
  7. data/lib/attachy/builders/attachy/form_builder.rb +15 -0
  8. data/lib/attachy/engine.rb +16 -0
  9. data/lib/attachy/helpers/attachy/view_helper.rb +31 -0
  10. data/lib/attachy/models/attachy/extension.rb +79 -0
  11. data/lib/attachy/models/attachy/file.rb +59 -0
  12. data/lib/attachy/models/attachy/viewer.rb +170 -0
  13. data/lib/attachy/version.rb +3 -0
  14. data/lib/generators/attachy/install_generator.rb +25 -0
  15. data/lib/generators/attachy/templates/config/attachy.yml.erb +15 -0
  16. data/lib/generators/attachy/templates/db/migrate/create_attachy_files_table.rb +19 -0
  17. data/lib/generators/attachy/templates/public/cloudinary_cors.html +46 -0
  18. data/spec/builders/attachy/form_builder/attachy_content_spec.rb +35 -0
  19. data/spec/builders/attachy/form_builder/attachy_file_field_spec.rb +23 -0
  20. data/spec/builders/attachy/form_builder/attachy_spec.rb +35 -0
  21. data/spec/factories/attachy/file.rb +12 -0
  22. data/spec/factories/user.rb +5 -0
  23. data/spec/helpers/attachy/attachy_content_spec.rb +21 -0
  24. data/spec/helpers/attachy/attachy_file_field_spec.rb +21 -0
  25. data/spec/helpers/attachy/attachy_spec.rb +35 -0
  26. data/spec/models/attachy/callback/destroy_file_spec.rb +55 -0
  27. data/spec/models/attachy/callback/remove_tmp_tag_spec.rb +11 -0
  28. data/spec/models/attachy/extension/user/avatar_spec.rb +91 -0
  29. data/spec/models/attachy/extension/user/photos_spec.rb +88 -0
  30. data/spec/models/attachy/file/config_spec.rb +11 -0
  31. data/spec/models/attachy/file/default_spec.rb +26 -0
  32. data/spec/models/attachy/file/path_spec.rb +16 -0
  33. data/spec/models/attachy/file/transform_spec.rb +86 -0
  34. data/spec/models/attachy/file_spec.rb +14 -0
  35. data/spec/models/attachy/viewer/attachments_spec.rb +28 -0
  36. data/spec/models/attachy/viewer/button_label_options_spec.rb +15 -0
  37. data/spec/models/attachy/viewer/button_label_spec.rb +34 -0
  38. data/spec/models/attachy/viewer/content_options_spec.rb +29 -0
  39. data/spec/models/attachy/viewer/content_spec.rb +62 -0
  40. data/spec/models/attachy/viewer/field_options_spec.rb +18 -0
  41. data/spec/models/attachy/viewer/field_spec.rb +56 -0
  42. data/spec/models/attachy/viewer/file_button_options_spec.rb +18 -0
  43. data/spec/models/attachy/viewer/file_button_spec.rb +57 -0
  44. data/spec/models/attachy/viewer/file_field_options_spec.rb +90 -0
  45. data/spec/models/attachy/viewer/file_field_spec.rb +25 -0
  46. data/spec/models/attachy/viewer/hidden_field_spec.rb +22 -0
  47. data/spec/models/attachy/viewer/image_spec.rb +170 -0
  48. data/spec/models/attachy/viewer/link_options_spec.rb +18 -0
  49. data/spec/models/attachy/viewer/link_spec.rb +131 -0
  50. data/spec/models/attachy/viewer/node_options_spec.rb +18 -0
  51. data/spec/models/attachy/viewer/node_spec.rb +134 -0
  52. data/spec/models/attachy/viewer/nodes_spec.rb +21 -0
  53. data/spec/models/attachy/viewer/remove_button_options_spec.rb +18 -0
  54. data/spec/models/attachy/viewer/transform_spec.rb +44 -0
  55. data/spec/models/attachy/viewer/value_spec.rb +83 -0
  56. data/spec/models/user_spec.rb +9 -0
  57. data/spec/rails_helper.rb +11 -0
  58. data/spec/support/common.rb +20 -0
  59. data/spec/support/database_cleaner.rb +19 -0
  60. data/spec/support/db/migrate/create_users_table.rb +7 -0
  61. data/spec/support/factory_girl.rb +7 -0
  62. data/spec/support/html_matchers.rb +5 -0
  63. data/spec/support/migrate.rb +4 -0
  64. data/spec/support/models/user.rb +5 -0
  65. data/spec/support/shoulda.rb +8 -0
  66. 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, '&#215;'.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,3 @@
1
+ module Attachy
2
+ VERSION = '0.1.0'.freeze
3
+ 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,15 @@
1
+ default: &default
2
+ default:
3
+ image:
4
+ format: <%= format %>
5
+ public_id: <%= public_id %>
6
+ version: <%= version %>
7
+
8
+ development:
9
+ <<: *default
10
+
11
+ production:
12
+ <<: *default
13
+
14
+ test:
15
+ <<: *default
@@ -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