datashift 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
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