saviour 0.3.0 → 0.3.1

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: 8673c1ddde4f8e16189bfd2d54ea7ad239155679
4
- data.tar.gz: 8a97558346780d9105a5b2145fcdc3a038ae3bc6
3
+ metadata.gz: fa3f38c97bf8c5fee8d2696496b167b7ea312367
4
+ data.tar.gz: 6ca9294640f87778851e17d87676273e4f183472
5
5
  SHA512:
6
- metadata.gz: 541052bdedf19b045140c57fa154d9f40007f56bd4fd4cd63895d99a2f577d24e4e10e39e876c597c1bc38fbd462797dcc59173d3b5f36d89aaa06e91c2cf52c
7
- data.tar.gz: beea334842a6e686c7df91b933ed55a70298fb00818a950381c2d015bc138ff90ff7deff9b7d74f0a4c150a8069600800f5afc5ff002d77b92e3e1cb0fde2380
6
+ metadata.gz: 0fa86cf2f8def862e78f0fb019cd90eb085495c4da0247a6b3fc97a8514b6277e1ec7252960ecb8a32cd6c242f520dde6406cbdb4e171ead83bff92b484fa3cd
7
+ data.tar.gz: ee7cf2411e9d881f2cd5c70fcc6a6bfa93e11b8c5197b9d73cff27a4823ad63991fdfe3a683c50e2bccc226c73f9fafcf43bf54c31a2b9f60d69881c38b5a160
data/.travis.yml CHANGED
@@ -5,6 +5,13 @@ rvm:
5
5
  - 2.1.8
6
6
  - 2.2.4
7
7
  - 2.3.0
8
+ - 2.4.0
9
+
10
+ gemfile:
11
+ - gemfiles/4.0.gemfile
12
+ - gemfiles/4.1.gemfile
13
+ - gemfiles/4.2.gemfile
14
+ - gemfiles/5.0.gemfile
8
15
 
9
16
  addons:
10
17
  code_climate:
@@ -12,3 +19,15 @@ addons:
12
19
 
13
20
  after_success:
14
21
  - bundle exec codeclimate-test-reporter
22
+
23
+ matrix:
24
+ exclude:
25
+ # rails 5 requires 2.2+
26
+ - rvm: 2.1.8
27
+ gemfile: gemfiles/5.0.gemfile
28
+
29
+ # Arel support for ruby 2.4 starts in 4.2
30
+ - rvm: 2.4.0
31
+ gemfile: gemfiles/4.0.gemfile
32
+ - rvm: 2.4.0
33
+ gemfile: gemfiles/4.1.gemfile
data/Appraisals ADDED
@@ -0,0 +1,19 @@
1
+ appraise "4.0" do
2
+ gem "activesupport", "~> 4.0.0"
3
+ gem "activerecord", "~> 4.0.0"
4
+ end
5
+
6
+ appraise "4.1" do
7
+ gem "activesupport", "~> 4.1.0"
8
+ gem "activerecord", "~> 4.1.0"
9
+ end
10
+
11
+ appraise "4.2" do
12
+ gem "activesupport", "~> 4.2.0"
13
+ gem "activerecord", "~> 4.2.0"
14
+ end
15
+
16
+ appraise "5.0" do
17
+ gem "activesupport", "~> 5.0.0"
18
+ gem "activerecord", "~> 5.0.0"
19
+ end
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
6
+ gem "activesupport", "~> 4.0.0"
7
+ gem "activerecord", "~> 4.0.0"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
6
+ gem "activesupport", "~> 4.1.0"
7
+ gem "activerecord", "~> 4.1.0"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
6
+ gem "activesupport", "~> 4.2.0"
7
+ gem "activerecord", "~> 4.2.0"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
6
+ gem "activesupport", "~> 5.0.0"
7
+ gem "activerecord", "~> 5.0.0"
8
+
9
+ gemspec :path => "../"
data/lib/saviour.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'fileutils'
2
2
  require 'fog/aws'
3
3
 
4
- require 'saviour/utils/class_attribute'
5
-
6
4
  require 'saviour/version'
7
5
  require 'saviour/base_uploader'
8
6
  require 'saviour/file'
@@ -11,12 +9,13 @@ require 'saviour/s3_storage'
11
9
  require 'saviour/config'
12
10
  require 'saviour/string_source'
13
11
  require 'saviour/url_source'
14
- require 'saviour/basic_model'
15
- require 'saviour/base_integrator'
12
+ require 'saviour/model'
13
+ require 'saviour/integrator'
16
14
  require 'saviour/attribute_name_calculator'
17
15
  require 'saviour/source_filename_extractor'
18
16
  require 'saviour/life_cycle'
19
-
17
+ require 'saviour/persistence_layer'
18
+ require 'saviour/validator'
20
19
 
21
20
  module Saviour
22
21
  end
@@ -19,7 +19,7 @@ module Saviour
19
19
  end
20
20
  end
21
21
 
22
- def respond_to?(name, *)
22
+ def respond_to_missing?(name, *)
23
23
  @data.key?(name) || super
24
24
  end
25
25
 
@@ -27,7 +27,9 @@ module Saviour
27
27
  store_dir = Uploader::StoreDirExtractor.new(self).store_dir
28
28
  raise RuntimeError, "Please use `store_dir` before trying to write" unless store_dir
29
29
 
30
- contents, filename = Uploader::ProcessorsRunner.new(self, @version_name).run!(contents, filename) if Config.processing_enabled
30
+ if Config.processing_enabled
31
+ contents, filename = Uploader::ProcessorsRunner.new(self, @version_name).run!(contents, filename)
32
+ end
31
33
 
32
34
  path = ::File.join(store_dir, filename)
33
35
  Config.storage.write(contents, path)
@@ -51,9 +53,9 @@ module Saviour
51
53
  element = Uploader::Element.new(@current_version, name || block)
52
54
 
53
55
  if block_given?
54
- processors.push({element: element, type: type})
56
+ processors.push(element: element, type: type)
55
57
  else
56
- processors.push({element: element, type: type, opts: opts})
58
+ processors.push(element: element, type: type, opts: opts)
57
59
  end
58
60
  end
59
61
 
@@ -1,21 +1,30 @@
1
1
  module Saviour
2
- class BaseIntegrator
3
- def initialize(klass)
2
+ class Integrator
3
+ def initialize(klass, persistence_klass)
4
4
  @klass = klass
5
+ @persistence_klass = persistence_klass
5
6
  end
6
7
 
7
8
  def file_instantiator_hook(model, file_instance, attach_as, version)
8
- # noop
9
+ layer = @persistence_klass.new(model)
10
+ file_instance.set_path!(layer.read(Saviour::AttributeNameCalculator.new(attach_as, version).name)) if layer.persisted?
9
11
  end
10
12
 
11
13
  def attach_file_hook(klass, attach_as, uploader_klass)
12
- # noop
14
+ versions = uploader_klass.versions || []
15
+
16
+ ([nil] + versions).each do |version|
17
+ column_name = Saviour::AttributeNameCalculator.new(attach_as, version).name
18
+
19
+ if klass.table_exists? && !klass.column_names.include?(column_name.to_s)
20
+ raise RuntimeError, "#{klass} must have a database string column named '#{column_name}'"
21
+ end
22
+ end
13
23
  end
14
24
 
15
25
  def setup!
16
26
  raise "You cannot include Saviour twice in the same class" if @klass.respond_to?(:attached_files)
17
27
 
18
- @klass.send :extend, ClassAttribute
19
28
  @klass.class_attribute :attached_files
20
29
  @klass.attached_files = {}
21
30
 
@@ -48,6 +57,15 @@ module Saviour
48
57
  end
49
58
  end
50
59
  end
60
+
61
+ @klass.class_attribute :__saviour_validations
62
+
63
+ class << @klass
64
+ def attach_validation(attach_as, method_name = nil, &block)
65
+ self.__saviour_validations ||= Hash.new { [] }
66
+ self.__saviour_validations[attach_as] += [method_name || block]
67
+ end
68
+ end
51
69
  end
52
70
  end
53
71
  end
@@ -63,7 +63,7 @@ module Saviour
63
63
  basedir = ::File.dirname(path)
64
64
  return if basedir == "."
65
65
 
66
- while basedir != "/" && Dir.entries(real_path(basedir)) == [".", ".."]
66
+ while basedir != "/" && Dir.entries(real_path(basedir)) - [".", ".."] ==[]
67
67
  Dir.rmdir(real_path(basedir))
68
68
  basedir = ::File.dirname(basedir)
69
69
  end
@@ -0,0 +1,24 @@
1
+ module Saviour
2
+ NoActiveRecordDetected = Class.new(StandardError)
3
+
4
+ module Model
5
+ def self.included(klass)
6
+ Integrator.new(klass, PersistenceLayer).setup!
7
+
8
+ klass.class_eval do
9
+ raise(NoActiveRecordDetected, "Error: ActiveRecord not detected in #{self}") unless self.ancestors.include?(ActiveRecord::Base)
10
+
11
+ after_destroy { Saviour::LifeCycle.new(self, PersistenceLayer).delete! }
12
+ after_save { Saviour::LifeCycle.new(self, PersistenceLayer).save! }
13
+ validate { Saviour::Validator.new(self).validate! }
14
+ end
15
+ end
16
+
17
+ def reload
18
+ self.class.attached_files.each do |attach_as, versions|
19
+ (versions + [nil]).each { |version| instance_variable_set("@__uploader_#{version}_#{attach_as}", nil) }
20
+ end
21
+ super
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Saviour
2
+ class PersistenceLayer
3
+ def initialize(model)
4
+ @model = model
5
+ end
6
+
7
+ def read(attr)
8
+ @model.read_attribute(attr)
9
+ end
10
+
11
+ def write(attr, value)
12
+ @model.update_columns(attr => value)
13
+ end
14
+
15
+ def persisted?
16
+ @model.persisted? || @model.destroyed?
17
+ end
18
+ end
19
+ end
@@ -6,7 +6,8 @@ module Saviour
6
6
  @conf = conf
7
7
  @overwrite_protection = conf.delete(:overwrite_protection) { true }
8
8
  @create_options = conf.delete(:create_options) { {} }
9
- assert_directory_exists!
9
+ conf.fetch(:aws_access_key_id) { raise ArgumentError.new("aws_access_key_id is required")}
10
+ conf.fetch(:aws_secret_access_key) { raise ArgumentError.new("aws_secret_access_key is required")}
10
11
  end
11
12
 
12
13
  def write(contents, path)
@@ -60,10 +61,6 @@ module Saviour
60
61
  path.gsub(/\A\/*/, '')
61
62
  end
62
63
 
63
- def assert_directory_exists!
64
- directory || raise(ArgumentError, "The bucket #{@bucket} doesn't exists or misconfigured connection.")
65
- end
66
-
67
64
  def assert_exists(path)
68
65
  raise RuntimeError, "File does not exists: #{path}" unless exists?(path)
69
66
  end
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'net/http'
2
3
 
3
4
  module Saviour
4
5
  class UrlSource
@@ -0,0 +1,50 @@
1
+ module Saviour
2
+ class Validator
3
+ def initialize(model)
4
+ raise "Please provide an object compatible with Saviour." unless model.class.respond_to?(:attached_files)
5
+
6
+ @model = model
7
+ end
8
+
9
+ def validate!
10
+ validations.each do |column, method_or_blocks|
11
+ raise(ArgumentError, "There is no attachment defined as '#{column}'") unless attached_files[column]
12
+ (attached_files[column] + [nil]).each do |version|
13
+ if @model.send(column, version).changed?
14
+ method_or_blocks.each { |method_or_block| run_validation(column, version, method_or_block) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def run_validation(column, version, method_or_block)
23
+ data = @model.send(column, version).source_data
24
+ filename = @model.send(column, version).filename_to_be_assigned
25
+ opts = {attached_as: column, version: version}
26
+
27
+ if method_or_block.respond_to?(:call)
28
+ if method_or_block.arity == 2
29
+ @model.instance_exec(data, filename, &method_or_block)
30
+ else
31
+ @model.instance_exec(data, filename, opts, &method_or_block)
32
+ end
33
+ else
34
+ if @model.method(method_or_block).arity == 2
35
+ @model.send(method_or_block, data, filename)
36
+ else
37
+ @model.send(method_or_block, data, filename, opts)
38
+ end
39
+ end
40
+ end
41
+
42
+ def attached_files
43
+ @model.class.attached_files || {}
44
+ end
45
+
46
+ def validations
47
+ @model.class.__saviour_validations || {}
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module Saviour
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
data/saviour.gemspec CHANGED
@@ -17,7 +17,12 @@ Gem::Specification.new do |spec|
17
17
 
18
18
  spec.add_dependency "fog-aws"
19
19
  spec.add_dependency "mime-types"
20
+ spec.add_dependency "activerecord", ">= 4.0"
21
+ spec.add_dependency "activesupport", ">= 4.0"
22
+
20
23
  spec.add_development_dependency "bundler"
21
- spec.add_development_dependency "rake"
22
24
  spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "sqlite3"
27
+ spec.add_development_dependency "appraisal"
23
28
  end
@@ -11,8 +11,8 @@ describe "access to model data from uploaders" do
11
11
  }
12
12
 
13
13
  let(:klass) {
14
- klass = Class.new {
15
- include Saviour::BasicModel
14
+ klass = Class.new(Test) {
15
+ include Saviour::Model
16
16
 
17
17
  def id
18
18
  87
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+
3
+ describe "saving a new file" do
4
+ before { allow(Saviour::Config).to receive(:storage).and_return(Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com")) }
5
+
6
+ let(:uploader) {
7
+ Class.new(Saviour::BaseUploader) {
8
+ store_dir { "/store/dir" }
9
+ }
10
+ }
11
+
12
+ let(:klass) {
13
+ a = Class.new(Test) { include Saviour::Model }
14
+ a.attach_file :file, uploader
15
+ a
16
+ }
17
+
18
+ describe "creation" do
19
+ it do
20
+ with_test_file("example.xml") do |example|
21
+ a = klass.create!
22
+ expect(a.update_attributes(file: example)).to be_truthy
23
+ end
24
+ end
25
+
26
+ it do
27
+ with_test_file("example.xml") do |example|
28
+ a = klass.create!
29
+ a.update_attributes(file: example)
30
+
31
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
32
+ end
33
+ end
34
+
35
+ it do
36
+ with_test_file("example.xml") do |example, real_filename|
37
+ a = klass.create!
38
+ a.update_attributes(file: example)
39
+ expect(a[:file]).to eq "/store/dir/#{real_filename}"
40
+ end
41
+ end
42
+
43
+ it do
44
+ with_test_file("example.xml") do |example|
45
+ a = klass.create!
46
+ a.update_attributes(file: example)
47
+
48
+ example.rewind
49
+ expect(a.file.read).to eq example.read
50
+ end
51
+ end
52
+
53
+ it do
54
+ with_test_file("example.xml") do |example|
55
+ a = klass.create!
56
+ a.update_attributes(file: example)
57
+
58
+ expect(a.file.exists?).to be_truthy
59
+ end
60
+ end
61
+
62
+ it do
63
+ with_test_file("example.xml") do |example, real_filename|
64
+ a = klass.create!
65
+ a.update_attributes(file: example)
66
+
67
+ expect(a.file.filename).to eq real_filename
68
+ end
69
+ end
70
+
71
+ it do
72
+ with_test_file("example.xml") do |example, real_filename|
73
+ a = klass.create!
74
+ a.update_attributes(file: example)
75
+
76
+ expect(a.file.url).to eq "http://domain.com/store/dir/#{real_filename}"
77
+ expect(a.file.public_url).to eq a.file.url
78
+ end
79
+ end
80
+
81
+ it "don't create anything if save do not completes (halt during before_save)" do
82
+ klass = Class.new(Test) do
83
+ attr_accessor :fail_at_save
84
+ before_save {
85
+ if ActiveRecord.version >= Gem::Version.new("5.0")
86
+ throw(:abort) if fail_at_save
87
+ else
88
+ !fail_at_save
89
+ end
90
+ }
91
+ include Saviour::Model
92
+ end
93
+ klass.attach_file :file, uploader
94
+
95
+ expect {
96
+ a = klass.new
97
+ a.fail_at_save = true
98
+ a.save!
99
+ }.to raise_error(ActiveRecord::RecordNotSaved)
100
+
101
+ with_test_file("example.xml") do |example, _|
102
+ a = klass.new
103
+ a.fail_at_save = true
104
+ a.file = example
105
+
106
+ expect(Saviour::Config.storage).not_to receive(:write)
107
+ a.save
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "deletion" do
113
+ it do
114
+ with_test_file("example.xml") do |example|
115
+ a = klass.create!
116
+ a.update_attributes(file: example)
117
+ expect(a.file.exists?).to be_truthy
118
+ expect(a.destroy).to be_truthy
119
+
120
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_falsey
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "updating" do
126
+ it do
127
+ with_test_file("example.xml") do |example|
128
+ a = klass.create!
129
+ a.update_attributes(file: example)
130
+
131
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
132
+ previous_location = a[:file]
133
+
134
+ with_test_file("camaloon.jpg") do |example_2|
135
+ a.update_attributes(file: example_2)
136
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
137
+
138
+ expect(Saviour::Config.storage.exists?(previous_location)).to be_falsey
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end