Class: Import

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/import.rb

Direct Known Subclasses

IssueImport, TimeEntryImport, UserImport

Constant Summary collapse

DATE_FORMATS =
[
  '%Y-%m-%d',
  '%d/%m/%Y',
  '%m/%d/%Y',
  '%Y/%m/%d',
  '%d.%m.%Y',
  '%d-%m-%Y'
]
AUTO_MAPPABLE_FIELDS =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Import

Returns a new instance of Import.



54
55
56
57
# File 'app/models/import.rb', line 54

def initialize(*args)
  super
  self.settings ||= {}
end

Class Method Details

.authorized?(user) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
# File 'app/models/import.rb', line 50

def self.authorized?(user)
  user.admin?
end

.layoutObject



46
47
48
# File 'app/models/import.rb', line 46

def self.layout
  'base'
end


42
43
44
# File 'app/models/import.rb', line 42

def self.menu_item
  nil
end

Instance Method Details

#add_callback(position, name, *args) ⇒ Object

Adds a callback that will be called after the item at given position is imported



175
176
177
178
179
180
# File 'app/models/import.rb', line 175

def add_callback(position, name, *args)
  settings['callbacks'] ||= {}
  settings['callbacks'][position] ||= []
  settings['callbacks'][position] << [name, args]
  save!
end

#columns_options(default = nil) ⇒ Object

Returns the headers as an array that can be used for select options



127
128
129
130
# File 'app/models/import.rb', line 127

def columns_options(default=nil)
  i = -1
  headers.map {|h| [h, i+=1]}
end

#do_callbacks(position, object) ⇒ Object

Executes the callbacks for the given object



183
184
185
186
187
188
189
190
# File 'app/models/import.rb', line 183

def do_callbacks(position, object)
  if callbacks = (settings['callbacks'] || {}).delete(position)
    callbacks.each do |name, args|
      send "#{name}_callback", object, *args
    end
    save!
  end
end

#file=(arg) ⇒ Object



59
60
61
62
63
64
# File 'app/models/import.rb', line 59

def file=(arg)
  return unless arg.present? && arg.size > 0

  self.filename = generate_filename
  Redmine::Utils.save_upload(arg, filepath)
end

#file_exists?Boolean

Returns true if the file to import exists

Returns:

  • (Boolean)


121
122
123
# File 'app/models/import.rb', line 121

def file_exists?
  filepath.present? && File.exist?(filepath)
end

#filepathObject

Returns the full path of the file to import It is stored in tmp/imports with a random hex as filename



112
113
114
115
116
117
118
# File 'app/models/import.rb', line 112

def filepath
  if filename.present? && /\A[0-9a-f]+\z/.match?(filename)
    File.join(Rails.root, "tmp", "imports", filename)
  else
    nil
  end
end

#first_rows(count = 4) ⇒ Object

Returns the count first rows of the file (including headers)



155
156
157
158
159
160
161
162
# File 'app/models/import.rb', line 155

def first_rows(count=4)
  rows = []
  read_rows do |row|
    rows << row
    break if rows.size >= count
  end
  rows
end

#headersObject

Returns an array of headers



165
166
167
# File 'app/models/import.rb', line 165

def headers
  first_rows(1).first || []
end

#mappingObject

Returns the mapping options



170
171
172
# File 'app/models/import.rb', line 170

def mapping
  settings['mapping'] || {}
end

#parse_fileObject

Parses the file to import and updates the total number of items



133
134
135
136
137
138
# File 'app/models/import.rb', line 133

def parse_file
  count = 0
  read_items {|row, i| count=i}
  update_attribute :total_items, count
  count
end

#read_itemsObject

Reads the items to import and yields the given block for each item



141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/models/import.rb', line 141

def read_items
  i = 0
  headers = true
  read_rows do |row|
    if i == 0 && headers
      headers = false
      next
    end
    i+= 1
    yield row, i if block_given?
  end
end

#run(options = {}) ⇒ Object

Imports items and returns the position of the last processed item



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'app/models/import.rb', line 193

def run(options={})
  max_items = options[:max_items]
  max_time = options[:max_time]
  current = 0
  imported = 0
  resume_after = items.maximum(:position) || 0
  interrupted = false
  started_on = Time.now

  read_items do |row, position|
    if (max_items && imported >= max_items) || (max_time && Time.now >= started_on + max_time)
      interrupted = true
      break
    end
    if position > resume_after
      item = items.build
      item.position = position
      item.unique_id = row_value(row, 'unique_id') if use_unique_id?

      if object = build_object(row, item)
        if object.save
          item.obj_id = object.id
        else
          item.message = object.errors.full_messages.join("\n")
        end
      end

      item.save!
      imported += 1

      extend_object(row, item, object) if object.persisted?
      do_callbacks(use_unique_id? ? item.unique_id : item.position, object)
    end
    current = position
  end

  if imported == 0 || interrupted == false
    if total_items.nil?
      update_attribute :total_items, current
    end
    update_attribute :finished, true
    remove_file
  end

  current
end

#saved_itemsObject



244
245
246
# File 'app/models/import.rb', line 244

def saved_items
  items.where("obj_id IS NOT NULL")
end

#set_default_settings(options = {}) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/models/import.rb', line 66

def set_default_settings(options={})
  separator = lu(user, :general_csv_separator)
  encoding = lu(user, :general_csv_encoding)
  if file_exists?
    begin
      content = File.read(filepath, 256)

      separator = [',', ';'].max_by {|sep| content.count(sep)}

      guessed_encoding = Redmine::CodesetUtil.guess_encoding(content)
      encoding =
        (guessed_encoding && (
          Setting::ENCODINGS.detect {|e| e.casecmp?(guessed_encoding)} ||
          Setting::ENCODINGS.detect {|e| Encoding.find(e) == Encoding.find(guessed_encoding)}
        )) || lu(user, :general_csv_encoding)
    rescue => e
    end
  end
  wrapper = '"'

  date_format = lu(user, "date.formats.default", :default => "foo")
  date_format = DATE_FORMATS.first unless DATE_FORMATS.include?(date_format)

  self.settings.merge!(
    'separator' => separator,
    'wrapper' => wrapper,
    'encoding' => encoding,
    'date_format' => date_format,
    'notifications' => '0'
  )

  if options.key?(:project_id) && !options[:project_id].blank?
    # Do not fail if project doesn't exist
    begin
      project = Project.find(options[:project_id])
      self.settings.merge!('mapping' => {'project_id' => project.id})
    rescue; end
  end
end

#to_paramObject



106
107
108
# File 'app/models/import.rb', line 106

def to_param
  filename
end

#unsaved_itemsObject



240
241
242
# File 'app/models/import.rb', line 240

def unsaved_items
  items.where(:obj_id => nil)
end