attache_rails 0.0.6 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23145e14f7f10f15b0ee5817d168619e2b6d0e5c
4
- data.tar.gz: cbe0855bc37ec5ab2137c3af923a53077cb4150e
3
+ metadata.gz: 0c7546fc76fade2bf70f9075fe87be7b71fffa6f
4
+ data.tar.gz: ee7f0526cbebe7292f685cb86ed501721d46f7b2
5
5
  SHA512:
6
- metadata.gz: a6e380a7dbb6fed113a7ad7e71f5af307dbcffaa39480e95054083053358d4bd76b04a4066e51d564387c5ebb949b05449173b1d2dd8f33a758916a92aaec599
7
- data.tar.gz: 2ac7cd485271384c199c2376b4ee163b62387a16e7cb5a9db584d0cf8a894c9450a926746e63c7daddd30917d5d614d55de07e7ff993384b22342b3b6edcf660
6
+ metadata.gz: 76d9c2a0aeec9e6de2980cd752bcf4e7fc83720426e073d60e85073e5d6a97e2ef4211733a0e415fd9af9280c49da0e7af52a8678bc72ce27548b6b442983290
7
+ data.tar.gz: 1b955d139c9fda1b3828e6f91a7ace1d8b213a92085cce02af4e0936c16b273558ee040082815dc93c391c1bc03530a5eed3a94ec9346d5b28e77999c488bf7d
data/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Ruby on Rails integration for [attache server](https://github.com/choonkeat/attache)
4
4
 
5
- NOTE: You can learn how to use this gem by looking at the [changes made to our example app](https://github.com/choonkeat/attache-railsapp/compare/fc47c17...master) or follow the step by step instructions in this `README`
6
-
7
5
  ## Dependencies
8
6
 
9
7
  [React](https://github.com/reactjs/react-rails), jQuery, Bootstrap 3
@@ -40,47 +38,38 @@ If you want to customize the file upload look and feel, define your own react `<
40
38
 
41
39
  ### Database
42
40
 
43
- To use `attache`, you only need to store the `path`, given to you after you've uploaded a file. So if you have an existing model, you only need to add a string, varchar or text field
41
+ 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
44
42
 
45
43
  ``` bash
46
- rails generate migration AddPhotoPathToUsers photo_path:string
44
+ rails generate migration AddPhotoPathToUsers photo:text
47
45
  ```
48
46
 
49
- To assign **multiple** images to **one** model, you'd only need one text field
47
+ To assign **multiple** images to **one** model, the same column can be used, although pluralized column name reads better
50
48
 
51
49
  ``` bash
52
- rails generate migration AddPhotoPathToUsers photo_path:text
50
+ rails generate migration AddPhotoPathToUsers photos:text
53
51
  ```
54
52
 
55
53
  ### Model
56
54
 
57
- In your model, `serialize` the column
55
+ In your model, define whether it `has_one_attache` or `has_many_attaches`
58
56
 
59
57
  ``` ruby
60
58
  class User < ActiveRecord::Base
61
- serialize :photo_path, JSON
59
+ has_many_attaches :photos
62
60
  end
63
61
  ```
64
62
 
65
63
  ### New or Edit form
66
64
 
67
- In your form, you would add some options to `file_field` using the `attache_options` helper method. For example, a regular file field may look like this:
65
+ In your form, you would add some options to `file_field` using the `attache_options` helper method:
68
66
 
69
67
  ``` slim
70
- = f.file_field :photo_path
68
+ = f.file_field :photos, f.object.avatar_options('64x64#')
71
69
  ```
72
70
 
73
- Change it to
74
-
75
- ``` slim
76
- = f.file_field :photo_path, **attache_options('64x64#', f.object.photo_path)
77
- ```
71
+ If you were using `has_many_attaches` the file input will automatically allow multiple files, otherwise the file input will only accept 1 file.
78
72
 
79
- Or if you're expecting multiple files uploaded, simply add `multiple: true`
80
-
81
- ``` slim
82
- = f.file_field :photo_path, multiple: true, **attache_options('64x64#', f.object.photo_path)
83
- ```
84
73
 
85
74
  NOTE: `64x64#` is just an example, you should define a suitable [geometry](http://www.imagemagick.org/Usage/resize/) for your form
86
75
 
@@ -98,45 +87,38 @@ If you're only accepting a single file upload, change it to
98
87
 
99
88
  ``` ruby
100
89
  def user_params
101
- params.require(:user).permit(:name, :photo_path)
90
+ params.require(:user).permit(:name, :photo, attaches_discarded: [])
102
91
  end
103
92
  ```
104
93
 
105
- If you're accepting multiple file uploads via `multiple: true`, change it to
94
+ If you're accepting multiple file uploads, change it to
106
95
 
107
96
  ``` ruby
108
97
  def user_params
109
- params.require(:user).permit(:name, photo_path: [])
98
+ params.require(:user).permit(:name, photos: [], attaches_discarded: [])
110
99
  end
111
100
  ```
112
101
 
102
+ NOTE: You do not have to manage `params[:attaches_discarded]` yourself. It is automatically managed for you between the frontend javascript and the ActiveRecord integration: files that are discarded will be removed from the attache server when you update or destroy your model.
103
+
113
104
  ### Show
114
105
 
115
- Use the `attache_urls` helper to obtain full urls for the values you've captured in your database.
106
+ Use the `*_url` or `*_urls` methods (depending on whether you are accepting multiple files) to obtain full urls.
116
107
 
117
108
  ``` slim
118
- - attache_urls(@user.photo_path, '128x128#') do |url|
119
- = image_tag(url)
109
+ = image_tag @user.photo_url('100x100#')
120
110
  ```
121
111
 
122
- You can also include `AttacheRails::Helper` directly in your model
123
-
124
- ``` ruby
125
- class User < ActiveRecord::Base
126
- include AttacheRails::Helper
127
- serialize :photo_path, JSON
112
+ or
128
113
 
129
- def photo_url
130
- attache_urls(photo_path, '128x128#').sample
131
- end
132
- end
114
+ ``` slim
115
+ - @user.photos_urls('200x200#').each do |url|
116
+ = image_tag url
133
117
  ```
134
118
 
135
119
  ### Environment configs
136
120
 
137
- `ATTACHE_UPLOAD_URL` points to the attache server upload url. e.g. `http://localhost:9292/upload`
138
-
139
- `ATTACHE_DOWNLOAD_URL` points to url prefix for downloading the resized images, e.g. `http://cdn.lvh.me:9292/view`
121
+ `ATTACHE_URL` points to the attache server. e.g. `http://localhost:9292`
140
122
 
141
123
  `ATTACHE_UPLOAD_DURATION` refers to the number of seconds before a signed upload request is considered expired, e.g. `600`
142
124
 
@@ -1,4 +1,4 @@
1
1
  //= require attache/cors_upload
2
- //= require attache/file_input
3
2
  //= require attache/bootstrap3
3
+ //= require attache/file_input
4
4
  //= require attache/ujs
@@ -46,3 +46,29 @@ if (typeof AttacheFilePreview === 'undefined') {
46
46
  });
47
47
 
48
48
  }
49
+
50
+ if (typeof AttachePlaceholder === 'undefined') {
51
+
52
+ var AttachePlaceholder = React.createClass({displayName: "AttachePlaceholder",
53
+ render: function() {
54
+ return (
55
+ React.createElement("div", {className: "attache-file-preview"},
56
+ React.createElement("img", {src: this.props.src})
57
+ )
58
+ );
59
+ }
60
+ });
61
+
62
+ }
63
+
64
+ if (typeof AttacheHeader === 'undefined') {
65
+
66
+ var AttacheHeader = React.createClass({displayName: "AttacheHeader",
67
+ render: function() {
68
+ return (
69
+ React.createElement("noscript", null)
70
+ );
71
+ }
72
+ });
73
+
74
+ }
@@ -46,3 +46,29 @@ if (typeof AttacheFilePreview === 'undefined') {
46
46
  });
47
47
 
48
48
  }
49
+
50
+ if (typeof AttachePlaceholder === 'undefined') {
51
+
52
+ var AttachePlaceholder = React.createClass({
53
+ render: function() {
54
+ return (
55
+ <div className="attache-file-preview">
56
+ <img src={this.props.src} />
57
+ </div>
58
+ );
59
+ }
60
+ });
61
+
62
+ }
63
+
64
+ if (typeof AttacheHeader === 'undefined') {
65
+
66
+ var AttacheHeader = React.createClass({
67
+ render: function() {
68
+ return (
69
+ <noscript />
70
+ );
71
+ }
72
+ });
73
+
74
+ }
@@ -14,7 +14,7 @@ var AttacheCORSUpload = (function() {
14
14
  }
15
15
 
16
16
  AttacheCORSUpload.prototype.handleFileSelect = function(file_element) {
17
- var f, files, output, _i, _len, _results, url, $ele;
17
+ var f, files, output, _i, _len, _results, url, $ele, prefix;
18
18
  $ele = $(file_element);
19
19
  url = $ele.data('uploadurl');
20
20
  if ($ele.data('hmac')) {
@@ -25,12 +25,13 @@ var AttacheCORSUpload = (function() {
25
25
  ""
26
26
  }
27
27
 
28
+ prefix = Date.now() + "_";
28
29
  files = file_element.files;
29
30
  output = [];
30
31
  _results = [];
31
32
  for (_i = 0, _len = files.length; _i < _len; _i++) {
32
33
  f = files[_i];
33
- f.uid = counter++;
34
+ f.uid = prefix + (counter++);
34
35
  this.onProgress(f.uid, { filename: f.name, percentLoaded: 0, bytesLoaded: 0, bytesTotal: f.size });
35
36
  _results.push(this.performUpload(f, url));
36
37
  }
@@ -2,25 +2,31 @@ var AttacheFileInput = React.createClass({displayName: "AttacheFileInput",
2
2
 
3
3
  getInitialState: function() {
4
4
  var files = {};
5
- var array = ([].concat(JSON.parse(this.props['data-value'])));
6
- $.each(array, function(uid, json) {
7
- if (json) files[uid] = { path: json };
5
+ if (this.props['data-value']) $.each(JSON.parse(this.props['data-value']), function(uid, json) {
6
+ if (json) files[uid] = json;
8
7
  });
9
- return {files: files};
8
+ return {files: files, attaches_discarded: []};
10
9
  },
11
10
 
12
11
  onRemove: function(uid, e) {
13
- delete this.state.files[uid];
14
- this.setState(this.state);
15
12
  e.preventDefault();
16
13
  e.stopPropagation();
14
+
15
+ var fieldname = this.getDOMNode().firstChild.name; // when 'user[avatar]'
16
+ var newfield = fieldname.replace(/\w+\](\[\]|)$/, 'attaches_discarded][]'); // become 'user[attaches_discarded][]'
17
+
18
+ this.state.attaches_discarded.push({ fieldname: newfield, path: this.state.files[uid].path });
19
+ delete this.state.files[uid];
20
+
21
+ this.setState(this.state);
17
22
  },
18
23
 
19
24
  onChange: function() {
20
25
  var file_element = this.getDOMNode().firstChild;
21
26
  // user cancelled file chooser dialog. ignore
22
27
  if (file_element.files.length == 0) return;
23
- this.state.files = {};
28
+ if (! this.props.multiple) this.state.files = {};
29
+
24
30
  this.setState(this.state);
25
31
  // upload the file via CORS
26
32
  var that = this;
@@ -45,26 +51,52 @@ var AttacheFileInput = React.createClass({displayName: "AttacheFileInput",
45
51
 
46
52
  render: function() {
47
53
  var that = this;
54
+
55
+ var Header = eval(this.props['data-header-component'] || 'AttacheHeader');
56
+ var Preview = eval(this.props['data-preview-component'] || 'AttacheFilePreview');
57
+ var Placeholder = eval(this.props['data-placeholder-component'] || 'AttachePlaceholder');
58
+
48
59
  var previews = [];
49
60
  $.each(that.state.files, function(key, result) {
50
- var json = JSON.stringify(result);
51
61
  if (result.path) {
52
62
  var parts = result.path.split('/');
53
63
  parts.splice(parts.length-1, 0, encodeURIComponent(that.props['data-geometry'] || '128x128#'));
54
64
  result.src = that.props['data-downloadurl'] + '/' + parts.join('/');
55
65
  result.filename = result.src.split('/').pop().split(/[#?]/).shift();
66
+ result.multiple = that.props.multiple;
56
67
  }
68
+ var json = JSON.stringify(result);
57
69
  previews.push(
58
70
  React.createElement("div", {className: "attache-file-input"},
59
- React.createElement("input", {type: "hidden", name: that.props.name, value: result.path, readOnly: "true"}),
60
- React.createElement(AttacheFilePreview, React.__spread({}, result, {key: key, onRemove: that.onRemove.bind(that, key)}))
71
+ React.createElement("input", {type: "hidden", name: that.props.name, value: json, readOnly: "true"}),
72
+ React.createElement(Preview, React.__spread({}, result, {key: key, onRemove: that.onRemove.bind(that, key)}))
61
73
  )
62
74
  );
63
75
  });
76
+
77
+ var placeholders = [];
78
+ if (previews.length == 0 && that.props['data-placeholder']) $.each(JSON.parse(that.props['data-placeholder']), function(uid, src) {
79
+ placeholders.push(
80
+ React.createElement(Placeholder, React.__spread({}, that.props, {src: src}))
81
+ );
82
+ });
83
+
84
+ var discards = [];
85
+ $.each(that.state.attaches_discarded, function(index, discard) {
86
+ discards.push(
87
+ React.createElement("input", {type: "hidden", name: discard.fieldname, value: discard.path})
88
+ );
89
+ });
90
+
91
+ var className = ["attache-file-selector", "attache-placeholders-count-" + placeholders.length, "attache-previews-count-" + previews.length, this.props['data-classname']].join(' ').trim();
64
92
  return (
65
- React.createElement("label", {htmlFor: this.props.id, className: "attache-file-selector"},
66
- React.createElement("input", React.__spread({type: "file"}, this.props, {onChange: this.onChange})),
67
- previews
93
+ React.createElement("label", {htmlFor: that.props.id, className: className},
94
+ React.createElement("input", React.__spread({type: "file"}, that.props, {onChange: this.onChange})),
95
+ React.createElement("input", {type: "hidden", name: that.props.name, value: ""}),
96
+ React.createElement(Header, React.__spread({}, that.props)),
97
+ previews,
98
+ placeholders,
99
+ discards
68
100
  )
69
101
  );
70
102
  }
@@ -2,25 +2,31 @@ var AttacheFileInput = React.createClass({
2
2
 
3
3
  getInitialState: function() {
4
4
  var files = {};
5
- var array = ([].concat(JSON.parse(this.props['data-value'])));
6
- $.each(array, function(uid, json) {
7
- if (json) files[uid] = { path: json };
5
+ if (this.props['data-value']) $.each(JSON.parse(this.props['data-value']), function(uid, json) {
6
+ if (json) files[uid] = json;
8
7
  });
9
- return {files: files};
8
+ return {files: files, attaches_discarded: []};
10
9
  },
11
10
 
12
11
  onRemove: function(uid, e) {
13
- delete this.state.files[uid];
14
- this.setState(this.state);
15
12
  e.preventDefault();
16
13
  e.stopPropagation();
14
+
15
+ var fieldname = this.getDOMNode().firstChild.name; // when 'user[avatar]'
16
+ var newfield = fieldname.replace(/\w+\](\[\]|)$/, 'attaches_discarded][]'); // become 'user[attaches_discarded][]'
17
+
18
+ this.state.attaches_discarded.push({ fieldname: newfield, path: this.state.files[uid].path });
19
+ delete this.state.files[uid];
20
+
21
+ this.setState(this.state);
17
22
  },
18
23
 
19
24
  onChange: function() {
20
25
  var file_element = this.getDOMNode().firstChild;
21
26
  // user cancelled file chooser dialog. ignore
22
27
  if (file_element.files.length == 0) return;
23
- this.state.files = {};
28
+ if (! this.props.multiple) this.state.files = {};
29
+
24
30
  this.setState(this.state);
25
31
  // upload the file via CORS
26
32
  var that = this;
@@ -45,26 +51,52 @@ var AttacheFileInput = React.createClass({
45
51
 
46
52
  render: function() {
47
53
  var that = this;
54
+
55
+ var Header = eval(this.props['data-header-component'] || 'AttacheHeader');
56
+ var Preview = eval(this.props['data-preview-component'] || 'AttacheFilePreview');
57
+ var Placeholder = eval(this.props['data-placeholder-component'] || 'AttachePlaceholder');
58
+
48
59
  var previews = [];
49
60
  $.each(that.state.files, function(key, result) {
50
- var json = JSON.stringify(result);
51
61
  if (result.path) {
52
62
  var parts = result.path.split('/');
53
63
  parts.splice(parts.length-1, 0, encodeURIComponent(that.props['data-geometry'] || '128x128#'));
54
64
  result.src = that.props['data-downloadurl'] + '/' + parts.join('/');
55
65
  result.filename = result.src.split('/').pop().split(/[#?]/).shift();
66
+ result.multiple = that.props.multiple;
56
67
  }
68
+ var json = JSON.stringify(result);
57
69
  previews.push(
58
70
  <div className="attache-file-input">
59
- <input type="hidden" name={that.props.name} value={result.path} readOnly="true" />
60
- <AttacheFilePreview {...result} key={key} onRemove={that.onRemove.bind(that, key)}/>
71
+ <input type="hidden" name={that.props.name} value={json} readOnly="true" />
72
+ <Preview {...result} key={key} onRemove={that.onRemove.bind(that, key)}/>
61
73
  </div>
62
74
  );
63
75
  });
76
+
77
+ var placeholders = [];
78
+ if (previews.length == 0 && that.props['data-placeholder']) $.each(JSON.parse(that.props['data-placeholder']), function(uid, src) {
79
+ placeholders.push(
80
+ <Placeholder {...that.props} src={src} />
81
+ );
82
+ });
83
+
84
+ var discards = [];
85
+ $.each(that.state.attaches_discarded, function(index, discard) {
86
+ discards.push(
87
+ <input type="hidden" name={discard.fieldname} value={discard.path} />
88
+ );
89
+ });
90
+
91
+ var className = ["attache-file-selector", "attache-placeholders-count-" + placeholders.length, "attache-previews-count-" + previews.length, this.props['data-classname']].join(' ').trim();
64
92
  return (
65
- <label htmlFor={this.props.id} className="attache-file-selector">
66
- <input type="file" {...this.props} onChange={this.onChange}/>
93
+ <label htmlFor={that.props.id} className={className}>
94
+ <input type="file" {...that.props} onChange={this.onChange}/>
95
+ <input type="hidden" name={that.props.name} value="" />
96
+ <Header {...that.props} />
67
97
  {previews}
98
+ {placeholders}
99
+ {discards}
68
100
  </label>
69
101
  );
70
102
  }
data/lib/attache_rails.rb CHANGED
@@ -1,43 +1,10 @@
1
- require "attache_rails/engine"
2
-
3
1
  module AttacheRails
4
- module Helper
5
- ATTACHE_UPLOAD_URL = ENV.fetch('ATTACHE_UPLOAD_URL') { 'http://localhost:9292/upload' }
6
- ATTACHE_DOWNLOAD_URL = ENV.fetch('ATTACHE_DOWNLOAD_URL') { 'http://localhost:9292/view' }
7
- ATTACHE_UPLOAD_DURATION = ENV.fetch('ATTACHE_UPLOAD_DURATION') { '600' }.to_i # 10 minutes
8
-
9
- def attache_urls(json, geometry)
10
- array = json.kind_of?(Array) ? json : [*json]
11
- array.collect do |path|
12
- download_url = ATTACHE_DOWNLOAD_URL
13
- prefix, basename = File.split(path)
14
- [download_url, prefix, CGI.escape(geometry), basename].join('/').tap do |url|
15
- yield url if block_given?
16
- end
17
- end
18
- end
19
-
20
- def attache_options(geometry, current_value)
21
- auth = if ENV['ATTACHE_SECRET_KEY']
22
- uuid = SecureRandom.uuid
23
- expiration = (Time.now + ATTACHE_UPLOAD_DURATION).to_i
24
- hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), ENV['ATTACHE_SECRET_KEY'], "#{uuid}#{expiration}")
25
- { uuid: uuid, expiration: expiration, hmac: hmac }
26
- else
27
- {}
28
- end
29
-
30
- {
31
- class: 'enable-attache',
32
- data: {
33
- geometry: geometry,
34
- value: [*current_value],
35
- uploadurl: ATTACHE_UPLOAD_URL,
36
- downloadurl: ATTACHE_DOWNLOAD_URL,
37
- }.merge(auth),
38
- }
39
- end
40
- end
2
+ ATTACHE_URL = ENV.fetch('ATTACHE_URL') { "http://localhost:9292" }
3
+ ATTACHE_UPLOAD_URL = ENV.fetch('ATTACHE_UPLOAD_URL') { "#{ATTACHE_URL}/upload" }
4
+ ATTACHE_DOWNLOAD_URL = ENV.fetch('ATTACHE_DOWNLOAD_URL') { "#{ATTACHE_URL}/view" }
5
+ ATTACHE_DELETE_URL = ENV.fetch('ATTACHE_DELETE_URL') { "#{ATTACHE_URL}/delete" }
6
+ ATTACHE_UPLOAD_DURATION = ENV.fetch('ATTACHE_UPLOAD_DURATION') { "600" }.to_i # signed upload form expires in 10 minutes
41
7
  end
42
8
 
43
- ActionView::Base.send(:include, AttacheRails::Helper)
9
+ require "attache_rails/engine"
10
+ require "attache_rails/model"
@@ -0,0 +1,91 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ module AttacheRails
5
+ module Utils
6
+ class << self
7
+ def attache_url_for(json_string, geometry)
8
+ JSON.parse(json_string).tap do |attrs|
9
+ prefix, basename = File.split(attrs['path'])
10
+ attrs['url'] = [ATTACHE_DOWNLOAD_URL, prefix, CGI.escape(geometry), basename].join('/')
11
+ end
12
+ end
13
+
14
+ def attache_auth_options
15
+ if ENV['ATTACHE_SECRET_KEY']
16
+ uuid = SecureRandom.uuid
17
+ expiration = (Time.now + ATTACHE_UPLOAD_DURATION).to_i
18
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), ENV['ATTACHE_SECRET_KEY'], "#{uuid}#{expiration}")
19
+ { uuid: uuid, expiration: expiration, hmac: hmac }
20
+ else
21
+ {}
22
+ end
23
+ end
24
+
25
+ def attache_options(geometry, current_value, options)
26
+ {
27
+ multiple: options[:multiple],
28
+ class: 'enable-attache',
29
+ data: {
30
+ geometry: geometry,
31
+ value: [*current_value],
32
+ placeholder: [*options[:placeholder]],
33
+ uploadurl: ATTACHE_UPLOAD_URL,
34
+ downloadurl: ATTACHE_DOWNLOAD_URL,
35
+ }.merge(options[:data_attrs] || {}).merge(attache_auth_options),
36
+ }
37
+ end
38
+ end
39
+ end
40
+ module Model
41
+ def self.included(base)
42
+ base.extend ClassMethods
43
+ base.class_eval do
44
+ attr_accessor :attaches_discarded
45
+ after_commit :attaches_discard!, if: :attaches_discarded
46
+ end
47
+ end
48
+
49
+ def attaches_discard!(files = attaches_discarded)
50
+ if files.present?
51
+ logger.info "DELETE #{files.inspect}"
52
+ Net::HTTP.post_form(
53
+ URI.parse(ATTACHE_DELETE_URL),
54
+ Utils.attache_auth_options.merge(paths: files.join("\n"))
55
+ )
56
+ end
57
+ end
58
+
59
+ module ClassMethods
60
+ def has_one_attache(name)
61
+ serialize name, JSON
62
+ define_method "#{name}_options", -> (geometry, options = {}) { Utils.attache_options(geometry, [self.send("#{name}_attributes", geometry)], multiple: false, **options) }
63
+ define_method "#{name}_url", -> (geometry) { self.send("#{name}_attributes", geometry).try(:[], 'url') }
64
+ define_method "#{name}_attributes", -> (geometry) { str = self.send(name); Utils.attache_url_for(str, geometry) if str; }
65
+ define_method "#{name}_discard", -> do
66
+ self.attaches_discarded ||= []
67
+ self.attaches_discarded.push(self.send("#{name}_attributes", 'original')['path'])
68
+ end
69
+ after_destroy "#{name}_discard"
70
+ end
71
+
72
+ def has_many_attaches(name)
73
+ serialize name, JSON
74
+ define_method "#{name}_options", -> (geometry, options = {}) { Utils.attache_options(geometry, self.send("#{name}_attributes", geometry), multiple: true, **options) }
75
+ define_method "#{name}_urls", -> (geometry) { self.send("#{name}_attributes", geometry).collect {|attrs| attrs['url'] } }
76
+ define_method "#{name}_attributes", -> (geometry) {
77
+ (self.send(name) || []).inject([]) do |sum, str|
78
+ sum + (str.blank? ? [] : [Utils.attache_url_for(str, geometry)])
79
+ end
80
+ }
81
+ define_method "#{name}_discard", -> do
82
+ self.attaches_discarded ||= []
83
+ self.send("#{name}_attributes", 'original').each {|attrs| self.attaches_discarded.push(attrs['path']) }
84
+ end
85
+ after_destroy "#{name}_discard"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ ActiveRecord::Base.send(:include, AttacheRails::Model)
@@ -1,3 +1,3 @@
1
1
  module AttacheRails
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  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.0.6
4
+ version: 0.1.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-03-01 00:00:00.000000000 Z
11
+ date: 2015-03-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - app/assets/javascripts/attache/ujs.js
30
30
  - lib/attache_rails.rb
31
31
  - lib/attache_rails/engine.rb
32
+ - lib/attache_rails/model.rb
32
33
  - lib/attache_rails/version.rb
33
34
  homepage: https://github.com/choonkeat/attache_rails
34
35
  licenses:
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
51
  version: '0'
51
52
  requirements: []
52
53
  rubyforge_project:
53
- rubygems_version: 2.4.5
54
+ rubygems_version: 2.4.6
54
55
  signing_key:
55
56
  specification_version: 4
56
57
  summary: Client lib to use attache server