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


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

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


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

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


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

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)

113
114
115
# File 'app/models/import.rb', line 113

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

#filepathObject

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


104
105
106
107
108
109
110
# File 'app/models/import.rb', line 104

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)


147
148
149
150
151
152
153
154
# File 'app/models/import.rb', line 147

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


157
158
159
# File 'app/models/import.rb', line 157

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

#mappingObject

Returns the mapping options


162
163
164
# File 'app/models/import.rb', line 162

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

#parse_fileObject

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


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

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


133
134
135
136
137
138
139
140
141
142
143
144
# File 'app/models/import.rb', line 133

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


185
186
187
188
189
190
191
192
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
# File 'app/models/import.rb', line 185

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


236
237
238
# File 'app/models/import.rb', line 236

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
# File 'app/models/import.rb', line 66

def set_default_settings(options={})
  separator = lu(user, :general_csv_separator)
  if file_exists?
    begin
      content = File.read(filepath, 256)
      separator = [',', ';'].sort_by {|sep| content.count(sep) }.last
    rescue => e
    end
  end
  wrapper = '"'
  encoding = lu(user, :general_csv_encoding)

  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


98
99
100
# File 'app/models/import.rb', line 98

def to_param
  filename
end

#unsaved_itemsObject


232
233
234
# File 'app/models/import.rb', line 232

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