datashift 0.10.1 → 0.10.2
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.
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/datashift.gemspec +13 -6
- data/lib/datashift.rb +2 -20
- data/lib/datashift/exceptions.rb +2 -0
- data/lib/datashift/method_detail.rb +15 -29
- data/lib/datashift/method_dictionary.rb +36 -21
- data/lib/datashift/method_mapper.rb +56 -16
- data/lib/datashift/populator.rb +23 -0
- data/lib/datashift/querying.rb +86 -0
- data/lib/generators/csv_generator.rb +1 -4
- data/lib/generators/excel_generator.rb +28 -11
- data/lib/generators/generator_base.rb +12 -0
- data/lib/loaders/csv_loader.rb +9 -3
- data/lib/loaders/excel_loader.rb +14 -6
- data/lib/loaders/loader_base.rb +38 -125
- data/lib/loaders/paperclip/attachment_loader.rb +130 -62
- data/lib/loaders/paperclip/datashift_paperclip.rb +46 -12
- data/lib/loaders/paperclip/image_loading.rb +25 -41
- data/lib/thor/generate.thor +16 -6
- data/lib/thor/paperclip.thor +25 -5
- data/spec/Gemfile +3 -2
- data/spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg +0 -0
- data/spec/{fixtures/images/DEMO_002_Powerstation.jpg → MissingAttachmentRecords/DEMO_002_Powerstation.jpeg} +0 -0
- data/spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg +0 -0
- data/spec/excel_generator_spec.rb +28 -0
- data/spec/excel_loader_spec.rb +12 -17
- data/spec/fixtures/config/database.yml +1 -1
- data/spec/fixtures/db/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/db/migrate/20121009161700_add_digitals.rb +24 -0
- data/spec/fixtures/images/DEMO_002_Powerstation.jpeg +0 -0
- data/spec/fixtures/models/digital.rb +14 -0
- data/spec/fixtures/models/owner.rb +5 -3
- data/spec/fixtures/test_model_defs.rb +4 -62
- data/spec/loader_spec.rb +42 -50
- data/spec/method_dictionary_spec.rb +3 -10
- data/spec/method_mapper_spec.rb +79 -20
- data/spec/paperclip_loader_spec.rb +95 -0
- data/spec/spec_helper.rb +44 -8
- metadata +236 -224
- data/lib/helpers/rake_utils.rb +0 -42
- data/spec/fixtures/models/test_model_defs.rb +0 -67
@@ -10,95 +10,163 @@ require 'datashift_paperclip'
|
|
10
10
|
|
11
11
|
module DataShift
|
12
12
|
|
13
|
-
|
13
|
+
module Paperclip
|
14
|
+
|
15
|
+
class AttachmentLoader < LoaderBase
|
14
16
|
|
15
|
-
|
17
|
+
include DataShift::Paperclip
|
16
18
|
|
17
|
-
|
19
|
+
attr_accessor :attach_to_klass, :attach_to_find_by_field
|
18
20
|
|
19
|
-
|
21
|
+
attr_writer :attach_to_field
|
22
|
+
attr_reader :attachment_path, :loading_files_cache
|
23
|
+
|
24
|
+
|
25
|
+
# If we have instantiated a method detail based on the attch to class and fields
|
26
|
+
# return that otherwise return the raw format of :attach_to_find_by_field
|
27
|
+
|
28
|
+
def attach_to_field
|
29
|
+
@attach_to_method_detail || @attach_to_field
|
30
|
+
end
|
31
|
+
|
32
|
+
# Options
|
33
|
+
|
34
|
+
# => :attach_to_klass
|
35
|
+
# A class that has a relationship with - has_many, has_one or belongs_to - the attachment
|
36
|
+
# The instance of :attach_to_klass can be searched for and the new attachment assigned.
|
37
|
+
# Examples
|
38
|
+
# Owner has_many pdfs and mp3 files as Digitals .... :attach_to_klass = Owner
|
39
|
+
# User has a single image used as an avatar ... :attach_to_klass = User
|
40
|
+
#
|
41
|
+
# => :attach_to_find_by_field
|
42
|
+
# For the :attach_to_klass, this is the field used to search for the parent
|
43
|
+
# object to assign the new attachment to.
|
44
|
+
# Examples
|
45
|
+
# Owner has a unique 'name' field ... :attach_to_find_by_field = :name
|
46
|
+
# User has a unique 'login' field ... :attach_to_klass = :login
|
47
|
+
#
|
48
|
+
# => :attach_to_field
|
49
|
+
# Attribute/association to assign attachment to on :attach_to_klass.
|
50
|
+
# Examples
|
51
|
+
# :attach_to_field => digitals : Owner.digitals = attachment
|
52
|
+
# :attach_to_field => avatar : User.avatar = attachment
|
53
|
+
#
|
54
|
+
#
|
55
|
+
def initialize(attachment_klazz, attachment = nil, options = {})
|
20
56
|
|
21
|
-
|
57
|
+
init_from_options( options )
|
58
|
+
|
59
|
+
opts = options.merge(:load => false)
|
22
60
|
|
23
|
-
|
61
|
+
super( attachment_klazz, attachment, opts )
|
62
|
+
|
63
|
+
puts "Attachment Class is #{load_object_class}" if(@verbose)
|
64
|
+
|
65
|
+
raise "Failed to create Attachment for loading" unless @load_object
|
66
|
+
end
|
67
|
+
|
68
|
+
def init_from_options( options )
|
69
|
+
|
70
|
+
@attach_to_klass = options[:attach_to_klass] || @attach_to_klass || nil
|
24
71
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
72
|
+
unless(@attach_to_klass.nil? || (MethodDictionary::for?(@attach_to_klass) && options[:reload] == false))
|
73
|
+
#puts "Building Method Dictionary for class #{object_class}"
|
74
|
+
DataShift::MethodDictionary.find_operators( @attach_to_klass, :reload => options[:reload], :instance_methods => true )
|
75
|
+
|
76
|
+
# Create dictionary of data on all possible 'setter' methods which can be used to
|
77
|
+
# populate or integrate an object of type @load_object_class
|
78
|
+
DataShift::MethodDictionary.build_method_details(@attach_to_klass)
|
79
|
+
end
|
31
80
|
|
32
|
-
|
81
|
+
@attach_to_find_by_field = options[:attach_to_find_by_field] || @attach_to_find_by_field || nil
|
82
|
+
@attach_to_field = options[:attach_to_field] || @attach_to_field || nil
|
83
|
+
|
84
|
+
unless(@attach_to_klass.nil? && @attach_to_field.nil? )
|
85
|
+
@attach_to_method_detail = MethodDictionary.find_method_detail(@attach_to_klass, @attach_to_field)
|
86
|
+
end
|
87
|
+
end
|
33
88
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
89
|
+
# This version creates attachments and also attaches them to instances of :attach_to_klazz
|
90
|
+
#
|
91
|
+
# Options
|
92
|
+
# :split_file_name_on Used in scan process to progresivly split filename to find
|
93
|
+
# :attach_to_klass with matching :attach_to_find_by_field
|
94
|
+
#
|
95
|
+
#
|
96
|
+
def process_from_filesystem(path, options = {} )
|
97
|
+
|
98
|
+
init_from_options( options )
|
39
99
|
|
40
|
-
|
100
|
+
raise "The class that attachments belongs to has not been set (:attach_to_klass)" unless(@attach_to_klass)
|
101
|
+
|
102
|
+
raise "The field to search for attachment's owner has not been set (:attach_to_find_by_field)" unless(@attach_to_find_by_field)
|
103
|
+
|
104
|
+
@load_object = options[:attachment] if(options[:attachment])
|
105
|
+
|
106
|
+
@attachment_path = path
|
41
107
|
|
42
|
-
|
108
|
+
missing_records = []
|
43
109
|
|
44
|
-
|
45
|
-
|
110
|
+
# try splitting up filename in various ways looking for the attachment owqner
|
111
|
+
split_on = @config['split_file_name_on'] || options[:split_file_name_on]
|
46
112
|
|
47
|
-
|
113
|
+
@loading_files_cache = Paperclip::get_files(path, options)
|
48
114
|
|
49
|
-
|
50
|
-
|
51
|
-
lookup_field = options[:attach_to_lookup_field]
|
52
|
-
|
53
|
-
cache.each do |file_name|
|
115
|
+
puts "Found #{loading_files_cache.size} files - splitting names on delimiter [#{split_on}]"
|
54
116
|
|
55
|
-
|
117
|
+
loading_files_cache.each do |file_name|
|
56
118
|
|
57
|
-
|
119
|
+
attachment_name = File.basename(file_name)
|
120
|
+
|
121
|
+
logger.info "Processing attachment file #{attachment_name} "
|
58
122
|
|
59
|
-
|
60
|
-
|
123
|
+
base_name = File.basename(file_name, '.*')
|
124
|
+
base_name.strip!
|
125
|
+
|
126
|
+
record = nil
|
61
127
|
|
62
|
-
|
63
|
-
|
64
|
-
record = get_record_by(@attach_to_klass, lookup_field, base_name, split_search_term)
|
128
|
+
puts "try to find record where #{attach_to_find_by_field} == #{base_name}"
|
129
|
+
record = get_record_by(attach_to_klass, attach_to_find_by_field, base_name, split_on)
|
65
130
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
131
|
+
if(record)
|
132
|
+
puts "Found record for attachment :\n#{record.inspect}"
|
133
|
+
logger.info "Found record for attachment : #{record.inspect}"
|
134
|
+
else
|
135
|
+
missing_records << file_name
|
136
|
+
end
|
71
137
|
|
72
|
-
|
138
|
+
next if(options[:dummy]) # Don't actually create/upload to DB if we are doing dummy run
|
73
139
|
|
74
|
-
|
75
|
-
|
76
|
-
|
140
|
+
# Check if attachment must have an associated record
|
141
|
+
if(record)
|
142
|
+
puts "now create attachment"
|
143
|
+
reset()
|
77
144
|
|
78
|
-
|
145
|
+
create_attachment(@load_object_class, file_name, record, attach_to_field, options)
|
79
146
|
|
80
|
-
|
81
|
-
|
147
|
+
puts "Added Attachment #{File.basename(file_name)} to #{record.send(attach_to_find_by_field)} : #{record.id}" if(@verbose)
|
148
|
+
end
|
82
149
|
|
83
|
-
|
150
|
+
end
|
84
151
|
|
85
|
-
|
86
|
-
|
152
|
+
unless missing_records.empty?
|
153
|
+
FileUtils.mkdir_p('MissingAttachmentRecords') unless File.directory?('MissingAttachmentRecords')
|
87
154
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
155
|
+
puts "WARNING : #{missing_records.size} of #{loading_files_cache.size} files could not be attached to a #{@load_object_class}"
|
156
|
+
puts "For your convenience a copy of files with MISSING #{@load_object_class} : ./MissingAttachmentRecords"
|
157
|
+
missing_records.each do |i|
|
158
|
+
puts "Copying #{i} to MissingAttachmentRecords folder" if(options[:verbose])
|
159
|
+
FileUtils.cp( i, 'MissingAttachmentRecords') unless(options[:dummy] == 'true')
|
160
|
+
end
|
161
|
+
else
|
162
|
+
puts "Created #{loading_files_cache.size} #{@load_object_class} attachments and succesfully attached to a #{@attach_to_klass}"
|
93
163
|
end
|
94
|
-
else
|
95
|
-
puts "All files (#{cache.size}) were succesfully attached to a #{@load_object_class}"
|
96
|
-
end
|
97
164
|
|
98
|
-
|
165
|
+
puts "Dummy Run Complete- if happy run without -d" if(options[:dummy])
|
99
166
|
|
100
|
-
|
167
|
+
end
|
101
168
|
|
102
|
-
|
169
|
+
end
|
103
170
|
|
104
|
-
end
|
171
|
+
end
|
172
|
+
end
|
@@ -6,12 +6,17 @@
|
|
6
6
|
# Details:: Module containing common functionality for working with Paperclip attachments
|
7
7
|
#
|
8
8
|
require 'logging'
|
9
|
+
require 'paperclip'
|
9
10
|
|
10
11
|
module DataShift
|
11
12
|
|
12
13
|
module Paperclip
|
13
14
|
|
14
15
|
include DataShift::Logging
|
16
|
+
include DataShift::Logging
|
17
|
+
require 'paperclip/attachment_loader'
|
18
|
+
|
19
|
+
attr_accessor :attachment
|
15
20
|
|
16
21
|
# Get all image files (based on file extensions) from supplied path.
|
17
22
|
# Options :
|
@@ -44,28 +49,57 @@ module DataShift
|
|
44
49
|
|
45
50
|
# Note the paperclip attachment model defines the storage path via something like :
|
46
51
|
# => :path => ":rails_root/public/blah/blahs/:id/:style/:basename.:extension"
|
52
|
+
#
|
47
53
|
# Options
|
48
|
-
#
|
54
|
+
#
|
55
|
+
# :attributes
|
56
|
+
#
|
57
|
+
# Pass through hash of attributes to klass initializer
|
58
|
+
#
|
59
|
+
# :has_attached_file_attribute
|
60
|
+
#
|
61
|
+
# Paperclip attachment name defined with macro 'has_attached_file :name'
|
62
|
+
#
|
49
63
|
# e.g
|
50
|
-
#
|
51
|
-
#
|
64
|
+
# When : has_attached_file :avatar
|
65
|
+
#
|
66
|
+
# Give : {:has_attached_file_attribute => :avatar}
|
67
|
+
#
|
68
|
+
# When : has_attached_file :icon
|
52
69
|
#
|
53
|
-
#
|
54
|
-
|
70
|
+
# Give : { :has_attached_file_attribute => :icon }
|
71
|
+
#
|
55
72
|
def create_attachment(klass, attachment_path, record = nil, attach_to_record_field = nil, options = {})
|
56
73
|
|
57
|
-
|
58
|
-
|
59
|
-
|
74
|
+
has_attached_file_attribute = options[has_attached_file_attribute] ? options[:has_attached_file_attribute].to_sym : :attachment
|
75
|
+
|
76
|
+
attributes = { has_attached_file_attribute => get_file(attachment_path) }
|
60
77
|
|
78
|
+
attributes.merge!(options[:attributes]) if(options[:attributes])
|
79
|
+
|
80
|
+
# e.g (:viewable => some_product) = Icon
|
81
|
+
|
82
|
+
#attributes.merge!(attach_to_record_field.to_sym => record) if(record && attach_to_record_field)
|
83
|
+
|
61
84
|
begin
|
62
85
|
|
63
|
-
attachment =
|
64
|
-
|
86
|
+
@attachment = klass.new(attributes, :without_protection => true)
|
87
|
+
|
88
|
+
if(@attachment.save)
|
89
|
+
puts "Success: Created Attachment #{@attachment.id} : #{@attachment.attachment_file_name}"
|
90
|
+
|
91
|
+
if(attach_to_record_field.is_a? MethodDetail)
|
92
|
+
attach_to_record_field.assign(record, @attachment)
|
93
|
+
else
|
94
|
+
# assume its not a has_many and try basic send
|
95
|
+
record.send(attach_to_record_field + '=', @attachment)
|
96
|
+
end if(record && attach_to_record_field)
|
97
|
+
|
65
98
|
else
|
66
|
-
|
99
|
+
puts "ERROR : Problem saving to DB : #{@attachment.inspect}"
|
67
100
|
end
|
68
|
-
|
101
|
+
|
102
|
+
@attachment
|
69
103
|
rescue => e
|
70
104
|
puts "PaperClip error - Problem creating Attachment from : #{attachment_path}"
|
71
105
|
puts e.inspect, e.backtrace
|
@@ -6,59 +6,43 @@
|
|
6
6
|
# => Provides facilities for bulk uploading/exporting attachments provided by PaperClip
|
7
7
|
# gem
|
8
8
|
require 'datashift_paperclip'
|
9
|
+
require 'attachment_loader'
|
9
10
|
|
10
11
|
module DataShift
|
11
12
|
|
12
13
|
module ImageLoading
|
13
14
|
|
14
|
-
include DataShift::Paperclip
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# :glob : The glob to use to find files
|
19
|
-
# => :recursive : Descend tree looking for files rather than just supplied path
|
20
|
-
|
21
|
-
def self.get_files(path, options = {})
|
22
|
-
glob = options[:glob] ? options[:glob] : image_magik_glob
|
23
|
-
glob = (options['recursive'] || options[:recursive]) ? "**/#{glob}" : glob
|
24
|
-
|
25
|
-
Dir.glob("#{path}/#{glob}", File::FNM_CASEFOLD)
|
15
|
+
include DataShift::Paperclip
|
16
|
+
|
17
|
+
def initialize(attachment_klazz, attachment = nil, options = {})
|
18
|
+
super( attachment_klazz, attachment, options )
|
26
19
|
end
|
27
|
-
|
28
20
|
|
29
21
|
# Note the paperclip attachment model defines the storage path via something like :
|
22
|
+
#
|
30
23
|
# => :path => ":rails_root/public/blah/blahs/:id/:style/:basename.:extension"
|
24
|
+
#
|
31
25
|
# Options
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
#
|
27
|
+
# See also DataShift::paperclip create_attachment for more options
|
28
|
+
#
|
29
|
+
# Example: Image is a model class with an attachment.
|
30
|
+
# Image table contains a viewable field which can contain other models,
|
31
|
+
# such as Product, User etc all of which can have an Image
|
32
|
+
#
|
33
|
+
# :viewable_record
|
34
|
+
#
|
35
|
+
def create_attachment(klass, attachment_path, record = nil, attach_to_record_field = nil, options = {})
|
36
|
+
|
37
|
+
attachment_options = options.dup
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
(viewable_record and viewable_record.respond_to? :name) ? viewable_record.name : ""
|
42
|
-
end
|
43
|
-
|
44
|
-
position = (viewable_record and viewable_record.respond_to?(:images)) ? viewable_record.images.length : 0
|
45
|
-
|
46
|
-
file = get_file(attachment_path)
|
39
|
+
attributes = {:alt => (options[:alt] || "") }
|
40
|
+
|
41
|
+
attributes[:position] = (!options[:position] && record and record.respond_to?(:images)) ? record.images.length : 0
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
{has_attached_file.to_sym => file, :viewable => viewable_record, :alt => alt, :position => position},
|
52
|
-
:without_protection => true
|
53
|
-
)
|
54
|
-
|
55
|
-
#image.attachment.reprocess! not sure this is required anymore
|
56
|
-
|
57
|
-
puts image.save ? "Success: Created Image: #{image.id} : #{image.attachment_file_name}" : "ERROR : Problem saving to DB Image: #{image.inspect}"
|
58
|
-
rescue => e
|
59
|
-
puts "PaperClip error - Problem creating an Image from : #{attachment_path}"
|
60
|
-
puts e.inspect, e.backtrace
|
61
|
-
end
|
43
|
+
attachment_options.merge!( attributes )
|
44
|
+
|
45
|
+
super(klass, attachment_path, record, attach_to_record_field, attachment_options)
|
62
46
|
end
|
63
47
|
|
64
48
|
# Set of file extensions ImageMagik can process so default glob
|
data/lib/thor/generate.thor
CHANGED
@@ -17,20 +17,24 @@
|
|
17
17
|
# bundle exec thor help datashift:generate:excel
|
18
18
|
#
|
19
19
|
require 'datashift'
|
20
|
-
|
20
|
+
require 'excel_generator'
|
21
|
+
|
21
22
|
# Note, not DataShift, case sensitive, create namespace for command line : datashift
|
22
23
|
module Datashift
|
23
24
|
|
24
25
|
|
25
26
|
class Generate < Thor
|
26
|
-
|
27
|
+
|
27
28
|
include DataShift::Logging
|
28
29
|
|
29
30
|
desc "excel", "generate a template from an active record model (with optional associations)"
|
30
31
|
method_option :model, :aliases => '-m', :required => true, :desc => "The active record model to export"
|
31
32
|
method_option :result, :aliases => '-r', :required => true, :desc => "Create template of model in supplied file"
|
32
33
|
method_option :assoc, :aliases => '-a', :type => :boolean, :desc => "Include all associations in the template"
|
33
|
-
method_option :exclude, :aliases => '-
|
34
|
+
method_option :exclude, :aliases => '-x', :type => :array, :desc => "Use with -a : Exclude association types. Any from #{DataShift::MethodDetail::supported_types_enum.to_a.inspect}"
|
35
|
+
method_option :remove, :aliases => '-e', :type => :array, :desc => "Don't generate in template the supplied fields"
|
36
|
+
method_option :remove_rails, :type => :boolean, :desc => "Don't generate in template the standard Rails fields: #{DataShift::GeneratorBase::rails_columns.inspect}"
|
37
|
+
|
34
38
|
|
35
39
|
def excel()
|
36
40
|
|
@@ -38,7 +42,7 @@ module Datashift
|
|
38
42
|
# ...can we make this more robust ? e.g what about when using active record but not in Rails app,
|
39
43
|
require File.expand_path('config/environment.rb')
|
40
44
|
|
41
|
-
|
45
|
+
|
42
46
|
|
43
47
|
model = options[:model]
|
44
48
|
result = options[:result]
|
@@ -58,12 +62,18 @@ module Datashift
|
|
58
62
|
begin
|
59
63
|
gen = DataShift::ExcelGenerator.new(result)
|
60
64
|
|
65
|
+
opts = { :remove => options[:remove],
|
66
|
+
:remove_rails => options[:remove_rails]
|
67
|
+
}
|
68
|
+
|
61
69
|
if(options[:assoc])
|
62
|
-
|
70
|
+
|
71
|
+
opts[:exclude] = options[:exclude]
|
72
|
+
|
63
73
|
logger.info("Datashift: Generating with associations")
|
64
74
|
gen.generate_with_associations(klass, opts)
|
65
75
|
else
|
66
|
-
gen.generate(klass)
|
76
|
+
gen.generate(klass, opts)
|
67
77
|
end
|
68
78
|
rescue => e
|
69
79
|
puts e
|