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.
Files changed (44) hide show
  1. data/Rakefile +6 -1
  2. data/VERSION +1 -1
  3. data/datashift.gemspec +13 -6
  4. data/lib/datashift.rb +2 -20
  5. data/lib/datashift/exceptions.rb +2 -0
  6. data/lib/datashift/method_detail.rb +15 -29
  7. data/lib/datashift/method_dictionary.rb +36 -21
  8. data/lib/datashift/method_mapper.rb +56 -16
  9. data/lib/datashift/populator.rb +23 -0
  10. data/lib/datashift/querying.rb +86 -0
  11. data/lib/generators/csv_generator.rb +1 -4
  12. data/lib/generators/excel_generator.rb +28 -11
  13. data/lib/generators/generator_base.rb +12 -0
  14. data/lib/loaders/csv_loader.rb +9 -3
  15. data/lib/loaders/excel_loader.rb +14 -6
  16. data/lib/loaders/loader_base.rb +38 -125
  17. data/lib/loaders/paperclip/attachment_loader.rb +130 -62
  18. data/lib/loaders/paperclip/datashift_paperclip.rb +46 -12
  19. data/lib/loaders/paperclip/image_loading.rb +25 -41
  20. data/lib/thor/generate.thor +16 -6
  21. data/lib/thor/paperclip.thor +25 -5
  22. data/spec/Gemfile +3 -2
  23. data/spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg +0 -0
  24. data/spec/{fixtures/images/DEMO_002_Powerstation.jpg → MissingAttachmentRecords/DEMO_002_Powerstation.jpeg} +0 -0
  25. data/spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpg +0 -0
  26. data/spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg +0 -0
  27. data/spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg +0 -0
  28. data/spec/excel_generator_spec.rb +28 -0
  29. data/spec/excel_loader_spec.rb +12 -17
  30. data/spec/fixtures/config/database.yml +1 -1
  31. data/spec/fixtures/db/datashift_test_models_db.sqlite +0 -0
  32. data/spec/fixtures/db/migrate/20121009161700_add_digitals.rb +24 -0
  33. data/spec/fixtures/images/DEMO_002_Powerstation.jpeg +0 -0
  34. data/spec/fixtures/models/digital.rb +14 -0
  35. data/spec/fixtures/models/owner.rb +5 -3
  36. data/spec/fixtures/test_model_defs.rb +4 -62
  37. data/spec/loader_spec.rb +42 -50
  38. data/spec/method_dictionary_spec.rb +3 -10
  39. data/spec/method_mapper_spec.rb +79 -20
  40. data/spec/paperclip_loader_spec.rb +95 -0
  41. data/spec/spec_helper.rb +44 -8
  42. metadata +236 -224
  43. data/lib/helpers/rake_utils.rb +0 -42
  44. 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
- class AttachmentLoader < LoaderBase
13
+ module Paperclip
14
+
15
+ class AttachmentLoader < LoaderBase
14
16
 
15
- include DataShift::Paperclip
17
+ include DataShift::Paperclip
16
18
 
17
- attr_accessor :attach_to_klass
19
+ attr_accessor :attach_to_klass, :attach_to_find_by_field
18
20
 
19
- def initialize(attachment_klazz, attachment = nil, options = {})
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
- opts = options.merge(:load => false)
57
+ init_from_options( options )
58
+
59
+ opts = options.merge(:load => false)
22
60
 
23
- super( attachment_klazz, attachment, opts )
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
- @attach_to_klass = options[:attach_to_klass]
26
-
27
- puts "Attachment Class is #{@attachment_klazz}" if(@verbose)
28
-
29
- raise "Failed to create Attachment for loading" unless @load_object
30
- end
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
- # :split_file_name_on
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
- def process_from_filesystem(path, options )
35
-
36
- @attach_to_klass = options[:attach_to_klazz] if(options[:attach_to_klazz])
37
-
38
- raise "The class that attachments belong to has not set" unless(@attach_to_klass)
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
- @attachment_path = path
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
- missing_records = []
108
+ missing_records = []
43
109
 
44
- # try splitting up filename in various ways looking for the SKU
45
- split_search_term = @config['split_file_name_on'] || options[:split_file_name_on]
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
- cache = Paperclip::get_files(@attachment_path, options)
113
+ @loading_files_cache = Paperclip::get_files(path, options)
48
114
 
49
- puts "Found #{cache.size} files - splitting names on delimiter [#{split_search_term}]"
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
- attachment_name = File.basename(file_name)
117
+ loading_files_cache.each do |file_name|
56
118
 
57
- logger.info "Processing attachment file #{attachment_name} "
119
+ attachment_name = File.basename(file_name)
120
+
121
+ logger.info "Processing attachment file #{attachment_name} "
58
122
 
59
- base_name = File.basename(file_name, '.*')
60
- base_name.strip!
123
+ base_name = File.basename(file_name, '.*')
124
+ base_name.strip!
125
+
126
+ record = nil
61
127
 
62
- record = nil
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
- if(record)
67
- logger.info "Found record for attachment : #{record.inspect}"
68
- else
69
- missing_records << file_name
70
- end
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
- next if(options[:dummy]) # Don't actually create/upload to DB if we are doing dummy run
138
+ next if(options[:dummy]) # Don't actually create/upload to DB if we are doing dummy run
73
139
 
74
- # Check if attachment must have an associated record
75
- if(record)
76
- reset()
140
+ # Check if attachment must have an associated record
141
+ if(record)
142
+ puts "now create attachment"
143
+ reset()
77
144
 
78
- create_attachment(@load_object_class, file_name, record, options[:attach_to_klass_field], options)
145
+ create_attachment(@load_object_class, file_name, record, attach_to_field, options)
79
146
 
80
- puts "Added Attachment #{File.basename(file_name)} to #{record.send(lookup_field)} : #{record.id}" if(@verbose)
81
- end
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
- end
150
+ end
84
151
 
85
- unless missing_records.empty?
86
- FileUtils.mkdir_p('MissingAttachmentRecords') unless File.directory?('MissingAttachmentRecords')
152
+ unless missing_records.empty?
153
+ FileUtils.mkdir_p('MissingAttachmentRecords') unless File.directory?('MissingAttachmentRecords')
87
154
 
88
- puts "WARNING : #{missing_records.size} of #{cache.size} files could not be attached to a #{@load_object_class}"
89
- puts "For your convenience a copy of files with MISSING #{@load_object_class} : ./MissingAttachmentRecords"
90
- missing_records.each do |i|
91
- puts "Copying #{i} to MissingAttachmentRecords folder" if(options[:verbose])
92
- FileUtils.cp( i, 'MissingAttachmentRecords') unless(options[:dummy] == 'true')
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
- puts "Dummy Run Complete- if happy run without -d" if(options[:dummy])
165
+ puts "Dummy Run Complete- if happy run without -d" if(options[:dummy])
99
166
 
100
- end
167
+ end
101
168
 
102
- end
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
- # has_attached_file_name : Paperclip attachment name defined with macro 'has_attached_file :name'
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
- # has_attached_file :avatar => options[:has_attached_file_name] = :avatar
51
- # has_attached_file :icon => options[:has_attached_file_name] = :icon
64
+ # When : has_attached_file :avatar
65
+ #
66
+ # Give : {:has_attached_file_attribute => :avatar}
67
+ #
68
+ # When : has_attached_file :icon
52
69
  #
53
- # alt : Alternatice text for images
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
- has_attached_file = options[:has_attached_file_name] ? options[:has_attached_file_name].to_sym : :attachment
58
-
59
- file = get_file(attachment_path)
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 = if(record && attach_to_record_field)
64
- klass.new( {has_attached_file => file}, :without_protection => true)
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
- klass.new( {has_attached_file => file, attach_to_record_field.to_sym => record}, :without_protection => true)
99
+ puts "ERROR : Problem saving to DB : #{@attachment.inspect}"
67
100
  end
68
- puts attachment.save ? "Success: Created #{attachment.id} : #{attachment.attachment_file_name}" : "ERROR : Problem saving to DB : #{attachment.inspect}"
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
- # Get all image files (based on file extensions) from supplied path.
17
- # Options :
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
- # has_attached_file_name : Paperclip attachment name defined with macro 'has_attached_file :name' e.g has_attached_file :avatar
33
- #
34
- def create_image(klass, attachment_path, viewable_record = nil, options = {})
35
-
36
- has_attached_file = options[:has_attached_file_name] || :attachment
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
- alt = if(options[:alt])
39
- options[:alt]
40
- else
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
- begin
49
-
50
- image = klass.new(
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
@@ -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 => '-e', :type => :array, :desc => "Use with -a : Exclude association types. Any from #{DataShift::MethodDetail::supported_types_enum.to_a.inspect}"
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
- require 'excel_generator'
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
- opts = (options[:exclude]) ? {:exclude => options[:exclude]} : {}
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