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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c05f1c9700fcff236abe554ee5bff029d799dff
4
- data.tar.gz: 688d2432bdb57cfa15cedafe56d0dec0deb461b5
3
+ metadata.gz: 0c5f0fbee1a5d05461d6a2653ee37c54b112280e
4
+ data.tar.gz: 185aee4a9db3412dc04ad22538c76a597a7f5472
5
5
  SHA512:
6
- metadata.gz: 48a55b14c595316bd139959a225f6d5e9d93b5da87ca5fd95089c7b1b144f482af0143b6ff995e32d88fb4a30c2974b7f446c3845a783e25a080cbebf807eca6
7
- data.tar.gz: 81d5d5005af6e89b301852a0af93a1aa3315d6f733ae64aa7bc6c7da323d8bb95dc5b894ef78799ef7da0b90ff44c87a0bc0e7491448022eb11e0b4360ad98b5
6
+ metadata.gz: a8b13faa02ed0ed274d854cdaca638979a7ddaee63f89edb95acd45470b0720e105c5132fd9999822cfa43d38409762cf94eb8ec9acf922c64cd80d29c126d06
7
+ data.tar.gz: c1a886578d9a3a7671a9ed463883d754d1a89c25376fbe86861b58e88c25069f23354a9d4cbcbb40cefbb92801398a72c6edc1914879b9ce79f1070ce5ff68bd
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # attache_rails
2
2
 
3
- Ruby on Rails integration for [attache server](https://github.com/choonkeat/attache)
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
@@ -1 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -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 :attaches_discard!, if: :attaches_discarded
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
- def attaches_discard!(files = attaches_discarded)
76
- files.reject!(&:blank?)
77
- files.uniq!
78
- if files.present?
79
- logger.info "DELETE #{files.inspect}"
80
- HTTPClient.post_content(
81
- URI.parse(ATTACHE_DELETE_URL),
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
- serialize name, JSON
93
- define_method "#{name}_options", -> (geometry, options = {}) { Utils.attache_options(geometry, Utils.array(self.send("#{name}_attributes", geometry)), multiple: false, **options) }
94
- define_method "#{name}_url", -> (geometry) { self.send("#{name}_attributes", geometry).try(:[], 'url') }
95
- define_method "#{name}_attributes", -> (geometry) { str = self.send(name); Utils.attache_url_for(str, geometry) if str; }
96
- define_method "#{name}=", -> (value) {
97
- new_value = (value.respond_to?(:read) ? Utils.attache_upload_and_get_json(value) : value)
98
- okay = JSON.parse(new_value)['path'] rescue nil
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
- serialize name, JSON
120
- define_method "#{name}_options", -> (geometry, options = {}) { Utils.attache_options(geometry, self.send("#{name}_attributes", geometry), multiple: true, **options) }
121
- define_method "#{name}_urls", -> (geometry) { self.send("#{name}_attributes", geometry).collect {|attrs| attrs['url'] } }
122
- define_method "#{name}_attributes", -> (geometry) {
123
- (self.send(name) || []).inject([]) do |sum, str|
124
- sum + Utils.array(str.present? && Utils.attache_url_for(str, geometry))
125
- end
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
@@ -1,3 +1,3 @@
1
1
  module AttacheRails
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.0"
3
3
  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.2.4
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-08-29 00:00:00.000000000 Z
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