attache_rails 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -2
- data/Rakefile +4 -0
- data/lib/attache_rails/model.rb +30 -128
- data/lib/attache_rails/version.rb +1 -1
- data/lib/generators/attache_rails/templates/upgrade_v2_to_v3_migration.rb.erb +45 -0
- data/lib/generators/attache_rails/upgrade_v2_to_v3_generator.rb +55 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c5f0fbee1a5d05461d6a2653ee37c54b112280e
|
4
|
+
data.tar.gz: 185aee4a9db3412dc04ad22538c76a597a7f5472
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8b13faa02ed0ed274d854cdaca638979a7ddaee63f89edb95acd45470b0720e105c5132fd9999822cfa43d38409762cf94eb8ec9acf922c64cd80d29c126d06
|
7
|
+
data.tar.gz: c1a886578d9a3a7671a9ed463883d754d1a89c25376fbe86861b58e88c25069f23354a9d4cbcbb40cefbb92801398a72c6edc1914879b9ce79f1070ce5ff68bd
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# attache_rails
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/attache_rails.svg)](https://badge.fury.io/rb/attache_rails)
|
4
|
+
[![Build Status](https://travis-ci.org/choonkeat/attache_rails.svg?branch=master)](https://travis-ci.org/choonkeat/attache_rails)
|
5
|
+
|
6
|
+
Ruby on Rails / ActiveRecord integration for [attache server](https://github.com/choonkeat/attache) using [attache/api](https://github.com/choonkeat/attache_api)
|
4
7
|
|
5
8
|
## Dependencies
|
6
9
|
|
@@ -8,6 +11,8 @@ Ruby on Rails integration for [attache server](https://github.com/choonkeat/atta
|
|
8
11
|
|
9
12
|
## Installation
|
10
13
|
|
14
|
+
**WARNING: Please see upgrade notes below if you are upgrading from V2**
|
15
|
+
|
11
16
|
Install the attache_rails package from Rubygems:
|
12
17
|
|
13
18
|
``` bash
|
@@ -44,7 +49,7 @@ $(document).on('cocoon:after-insert', AttacheRails.upgrade_fileinputs);
|
|
44
49
|
|
45
50
|
### Database
|
46
51
|
|
47
|
-
To use `attache`, you only need to store the JSON attributes given to you after you've uploaded a file. So if you have an existing model, you only need to add a text column
|
52
|
+
To use `attache`, you only need to store the JSON attributes given to you after you've uploaded a file. So if you have an existing model, you only need to add a text column (PostgreSQL users see below)
|
48
53
|
|
49
54
|
``` bash
|
50
55
|
rails generate migration AddPhotoPathToUsers photo:text
|
@@ -133,6 +138,35 @@ or
|
|
133
138
|
* If this variable is not set, then upload requests will not be signed & `ATTACHE_UPLOAD_DURATION` will be ignored
|
134
139
|
* If this variable is set, it must be the same value as `SECRET_KEY` is set on the `attache` server
|
135
140
|
|
141
|
+
### PostgreSQL
|
142
|
+
|
143
|
+
Take advantage of the [json support](http://guides.rubyonrails.org/active_record_postgresql.html#json) by using the [`json` or `jsonb` column types](http://www.postgresql.org/docs/9.4/static/functions-json.html) instead
|
144
|
+
|
145
|
+
``` bash
|
146
|
+
rails generate migration AddPhotoPathToUsers photo:json
|
147
|
+
```
|
148
|
+
|
149
|
+
This opens up the possibility to query inside the column, e.g.
|
150
|
+
|
151
|
+
``` ruby
|
152
|
+
User.where("photo ->> 'content_type' = ?", 'image/png')
|
153
|
+
```
|
154
|
+
|
155
|
+
## Upgrading from v2
|
156
|
+
|
157
|
+
`json` values in the database column has changed in v3. Previously, we are working with json *strings*, now we are working with json *objects*, aka `Hash`. i.e.
|
158
|
+
|
159
|
+
- in previous version, `@user.photos` value could be something like `["{\"path\":\"dirname/file.jpg\"}"]`. Notice that it is an array of 1 `String`.
|
160
|
+
- in v3, the value would be `[{"path"=>"dirname/file.jpg"}]`. Notice that it is an array of 1 `Hash`
|
161
|
+
|
162
|
+
If you're upgrading from V2, we have a generator that will create a migration file to fixup the data
|
163
|
+
|
164
|
+
```
|
165
|
+
rails g attache_rails:upgrade_v2_to_v3
|
166
|
+
```
|
167
|
+
|
168
|
+
NOTE: It is highly recommended that developers verify the migration with a dump of the production data in a staging environment. Please take a look at the generated migration file.
|
169
|
+
|
136
170
|
# License
|
137
171
|
|
138
172
|
MIT
|
data/Rakefile
CHANGED
data/lib/attache_rails/model.rb
CHANGED
@@ -1,150 +1,52 @@
|
|
1
1
|
require "cgi"
|
2
2
|
require "uri"
|
3
3
|
require "httpclient"
|
4
|
+
require "attache/api"
|
4
5
|
|
5
6
|
module AttacheRails
|
6
|
-
module Utils
|
7
|
-
class << self
|
8
|
-
def array(value)
|
9
|
-
Array.wrap(value).reject(&:blank?)
|
10
|
-
end
|
11
|
-
|
12
|
-
def attache_retry_doing(max_retries, retries = 0)
|
13
|
-
yield
|
14
|
-
rescue Exception
|
15
|
-
if (retries += 1) <= max_retries
|
16
|
-
sleep retries
|
17
|
-
retry
|
18
|
-
end
|
19
|
-
raise
|
20
|
-
end
|
21
|
-
|
22
|
-
def attache_upload_and_get_json(readable)
|
23
|
-
uri = URI.parse(ATTACHE_UPLOAD_URL)
|
24
|
-
original_filename = readable.try(:original_filename) ||
|
25
|
-
readable.try(:path) && File.basename(readable.try(:path)) ||
|
26
|
-
'noname'
|
27
|
-
uri.query = { file: original_filename, **attache_auth_options }.collect {|k,v|
|
28
|
-
CGI.escape(k.to_s) + "=" + CGI.escape(v.to_s)
|
29
|
-
}.join('&')
|
30
|
-
attache_retry_doing(3) { HTTPClient.post(uri, readable, {'Content-Type' => 'binary/octet-stream'}).body }
|
31
|
-
end
|
32
|
-
|
33
|
-
def attache_url_for(json_string, geometry)
|
34
|
-
JSON.parse(json_string).tap do |attrs|
|
35
|
-
prefix, basename = File.split(attrs['path'])
|
36
|
-
attrs['url'] = [ATTACHE_DOWNLOAD_URL, prefix, CGI.escape(geometry), CGI.escape(basename)].join('/')
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def attache_auth_options
|
41
|
-
if ENV['ATTACHE_SECRET_KEY']
|
42
|
-
uuid = SecureRandom.uuid
|
43
|
-
expiration = (Time.now + ATTACHE_UPLOAD_DURATION).to_i
|
44
|
-
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), ENV['ATTACHE_SECRET_KEY'], "#{uuid}#{expiration}")
|
45
|
-
{ uuid: uuid, expiration: expiration, hmac: hmac }
|
46
|
-
else
|
47
|
-
{}
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def attache_options(geometry, current_value, options)
|
52
|
-
{
|
53
|
-
multiple: options[:multiple],
|
54
|
-
class: 'enable-attache',
|
55
|
-
data: {
|
56
|
-
geometry: geometry,
|
57
|
-
value: [*current_value],
|
58
|
-
placeholder: [*options[:placeholder]],
|
59
|
-
uploadurl: ATTACHE_UPLOAD_URL,
|
60
|
-
downloadurl: ATTACHE_DOWNLOAD_URL,
|
61
|
-
}.merge(options[:data] || {}).merge(attache_auth_options),
|
62
|
-
}
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
7
|
module Model
|
8
|
+
include Attache::API::Model
|
9
|
+
|
67
10
|
def self.included(base)
|
11
|
+
# has_one_attache, has_many_attaches
|
68
12
|
base.extend ClassMethods
|
13
|
+
|
14
|
+
# `discard` management
|
69
15
|
base.class_eval do
|
70
16
|
attr_accessor :attaches_discarded
|
71
|
-
after_commit
|
17
|
+
after_commit if: :attaches_discarded do |instance|
|
18
|
+
instance.attaches_discard!(instance.attaches_discarded)
|
19
|
+
end
|
72
20
|
end
|
73
21
|
end
|
74
22
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
Utils.attache_auth_options.merge(paths: files.join("\n"))
|
83
|
-
)
|
23
|
+
module ClassMethods
|
24
|
+
def attache_setup_column(name)
|
25
|
+
case coltype = column_for_attribute(name).type
|
26
|
+
when :text, :string, :binary
|
27
|
+
serialize name, JSON
|
28
|
+
end
|
29
|
+
rescue Exception
|
84
30
|
end
|
85
|
-
rescue Exception
|
86
|
-
raise if ENV['ATTACHE_DISCARD_FAILURE_RAISE_ERROR']
|
87
|
-
logger.warn [$!, $@]
|
88
|
-
end
|
89
31
|
|
90
|
-
module ClassMethods
|
91
32
|
def has_one_attache(name)
|
92
|
-
|
93
|
-
define_method "#{name}_options", -> (geometry, options = {}) {
|
94
|
-
define_method "#{name}_url", -> (geometry)
|
95
|
-
define_method "#{name}_attributes", -> (geometry)
|
96
|
-
define_method "#{name}=", -> (value)
|
97
|
-
|
98
|
-
|
99
|
-
super(Utils.array(okay ? new_value : nil).first)
|
100
|
-
}
|
101
|
-
define_method "#{name}_discard_was",-> do
|
102
|
-
new_value = self.send("#{name}")
|
103
|
-
old_value = self.send("#{name}_was")
|
104
|
-
obsoleted = Utils.array(old_value).collect {|x| JSON.parse(x)['path'] } - Utils.array(new_value).collect {|x| JSON.parse(x)['path'] }
|
105
|
-
self.attaches_discarded ||= []
|
106
|
-
self.attaches_discarded.push(*obsoleted)
|
107
|
-
end
|
108
|
-
after_update "#{name}_discard_was"
|
109
|
-
define_method "#{name}_discard", -> do
|
110
|
-
self.attaches_discarded ||= []
|
111
|
-
if attrs = self.send("#{name}_attributes", 'original')
|
112
|
-
self.attaches_discarded.push(attrs['path'])
|
113
|
-
end
|
114
|
-
end
|
115
|
-
after_destroy "#{name}_discard"
|
33
|
+
attache_setup_column(name)
|
34
|
+
define_method "#{name}_options", -> (geometry, options = {}) { Hash(class: 'enable-attache', multiple: false).merge(attache_field_options(self.send(name), geometry, options)) }
|
35
|
+
define_method "#{name}_url", -> (geometry) { attache_field_urls(self.send(name), geometry).try(:first) }
|
36
|
+
define_method "#{name}_attributes", -> (geometry) { attache_field_attributes(self.send(name), geometry).try(:first) }
|
37
|
+
define_method "#{name}=", -> (value) { super(attache_field_set(Array.wrap(value)).try(:first)) }
|
38
|
+
after_update -> { self.attaches_discarded ||= []; attache_mark_for_discarding(self.send("#{name}_was"), self.send("#{name}"), self.attaches_discarded) }
|
39
|
+
after_destroy -> { self.attaches_discarded ||= []; attache_mark_for_discarding(self.send("#{name}_was"), [], self.attaches_discarded) }
|
116
40
|
end
|
117
41
|
|
118
42
|
def has_many_attaches(name)
|
119
|
-
|
120
|
-
define_method "#{name}_options", -> (geometry, options = {}) {
|
121
|
-
define_method "#{name}_urls", -> (geometry)
|
122
|
-
define_method "#{name}_attributes", -> (geometry)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
}
|
127
|
-
define_method "#{name}=", -> (array) {
|
128
|
-
new_value = Utils.array(array).inject([]) {|sum,value|
|
129
|
-
hash = value.respond_to?(:read) ? Utils.attache_upload_and_get_json(value) : value
|
130
|
-
okay = JSON.parse(hash)['path'] rescue nil
|
131
|
-
okay ? sum + [hash] : sum
|
132
|
-
}
|
133
|
-
super(Utils.array new_value)
|
134
|
-
}
|
135
|
-
define_method "#{name}_discard_was",-> do
|
136
|
-
new_value = [*self.send("#{name}")]
|
137
|
-
old_value = [*self.send("#{name}_was")]
|
138
|
-
obsoleted = old_value.collect {|x| JSON.parse(x)['path'] } - new_value.collect {|x| JSON.parse(x)['path'] }
|
139
|
-
self.attaches_discarded ||= []
|
140
|
-
obsoleted.each {|path| self.attaches_discarded.push(path) }
|
141
|
-
end
|
142
|
-
after_update "#{name}_discard_was"
|
143
|
-
define_method "#{name}_discard", -> do
|
144
|
-
self.attaches_discarded ||= []
|
145
|
-
self.send("#{name}_attributes", 'original').each {|attrs| self.attaches_discarded.push(attrs['path']) }
|
146
|
-
end
|
147
|
-
after_destroy "#{name}_discard"
|
43
|
+
attache_setup_column(name)
|
44
|
+
define_method "#{name}_options", -> (geometry, options = {}) { Hash(class: 'enable-attache', multiple: true).merge(attache_field_options(self.send(name), geometry, options)) }
|
45
|
+
define_method "#{name}_urls", -> (geometry) { attache_field_urls(self.send(name), geometry) }
|
46
|
+
define_method "#{name}_attributes", -> (geometry) { attache_field_attributes(self.send(name), geometry) }
|
47
|
+
define_method "#{name}=", -> (value) { super(attache_field_set(Array.wrap(value))) }
|
48
|
+
after_update -> { self.attaches_discarded ||= []; attache_mark_for_discarding(self.send("#{name}_was"), self.send("#{name}"), self.attaches_discarded) }
|
49
|
+
after_destroy -> { self.attaches_discarded ||= []; attache_mark_for_discarding(self.send("#{name}_was"), [], self.attaches_discarded) }
|
148
50
|
end
|
149
51
|
end
|
150
52
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
<%- ($has_one_attache + $has_many_attaches).each do |(klass, column)| -%>
|
3
|
+
class <%= klass %> < ActiveRecord::Base; serialize :<%= column %>, JSON; end
|
4
|
+
<%- end -%>
|
5
|
+
|
6
|
+
def self.up
|
7
|
+
# has_one_attache
|
8
|
+
<%- $has_one_attache.each do |(klass, column)| -%>
|
9
|
+
<%= klass %>.where.not(<%= column %>: [nil, ""]).find_each do |obj|
|
10
|
+
if obj[:<%= column %>].kind_of?(String)
|
11
|
+
obj.update(<%= column %>: JSON.parse(obj[:<%= column %>]))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
<%- end -%>
|
15
|
+
|
16
|
+
# has_many_attaches
|
17
|
+
<%- $has_many_attaches.each do |(klass, column)| -%>
|
18
|
+
<%= klass %>.where.not(<%= column %>: [nil, ""]).find_each do |obj|
|
19
|
+
if obj[:<%= column %>].first.kind_of?(String)
|
20
|
+
obj.update(<%= column %>: obj[:<%= column %>].collect {|v| JSON.parse(v) })
|
21
|
+
end
|
22
|
+
end
|
23
|
+
<%- end -%>
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down
|
27
|
+
# has_one_attache
|
28
|
+
<%- $has_one_attache.each do |(klass, column)| -%>
|
29
|
+
<%= klass %>.where.not(<%= column %>: [nil, ""]).find_each do |obj|
|
30
|
+
if obj[:<%= column %>].kind_of?(Hash)
|
31
|
+
obj.update(<%= column %>: obj[:<%= column %>].to_json)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
<%- end -%>
|
35
|
+
|
36
|
+
# has_many_attaches
|
37
|
+
<%- $has_many_attaches.each do |(klass, column)| -%>
|
38
|
+
<%= klass %>.where.not(<%= column %>: [nil, ""]).find_each do |obj|
|
39
|
+
if obj[:<%= column %>].first.kind_of?(Hash)
|
40
|
+
obj.update(<%= column %>: obj[:<%= column %>].collect {|v| v.to_json })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
<%- end -%>
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module AttacheRails
|
4
|
+
class UpgradeV2ToV3Generator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
def self.next_migration_number(dir)
|
8
|
+
ActiveRecord::Generators::Base.next_migration_number(dir)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.source_root
|
12
|
+
@source_root ||= File.expand_path('../templates', __FILE__)
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_migration
|
16
|
+
migration_template "upgrade_v2_to_v3_migration.rb.erb", "db/migrate/#{migration_file_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def migration_name
|
20
|
+
"UpgradeAttacheFieldsFromV2ToV3"
|
21
|
+
end
|
22
|
+
|
23
|
+
def migration_file_name
|
24
|
+
"#{migration_name.underscore}.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
def migration_class_name
|
28
|
+
migration_name.camelize
|
29
|
+
end
|
30
|
+
|
31
|
+
$has_one_attache = []
|
32
|
+
$has_many_attaches = []
|
33
|
+
|
34
|
+
ActiveRecord::Base.class_eval do
|
35
|
+
def self.has_one_attache(name)
|
36
|
+
$has_one_attache.push([self.name, name])
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.has_many_attaches(name)
|
40
|
+
$has_many_attaches.push([self.name, name])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Rails::ConsoleMethods#reload!
|
45
|
+
ActionDispatch::Reloader.cleanup!
|
46
|
+
ActionDispatch::Reloader.prepare!
|
47
|
+
|
48
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
49
|
+
if klass = table.classify.safe_constantize
|
50
|
+
else
|
51
|
+
puts "skipped #{table}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attache_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- choonkeat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: attache_api
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: httpclient
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,6 +129,8 @@ files:
|
|
115
129
|
- lib/attache_rails/engine.rb
|
116
130
|
- lib/attache_rails/model.rb
|
117
131
|
- lib/attache_rails/version.rb
|
132
|
+
- lib/generators/attache_rails/templates/upgrade_v2_to_v3_migration.rb.erb
|
133
|
+
- lib/generators/attache_rails/upgrade_v2_to_v3_generator.rb
|
118
134
|
homepage: https://github.com/choonkeat/attache_rails
|
119
135
|
licenses:
|
120
136
|
- MIT
|