Class: ListExport

Inherits:
Object
  • Object
show all
Defined in:
app/models/list_export.rb

Overview

This class helps to export data to CSV, XLS and possibly others.

Example:

class PeopleController
  def index
    # ...
    format.xls do
      send_data ListExport.new(@people, :birthday_list).to_xls
    end
  end
end

The following ressources might be helpful.

* https://github.com/splendeo/to_xls
* https://github.com/zdavatz/spreadsheet
* Formatting xls: http://scm.ywesee.com/?p=spreadsheet/.git;a=blob;f=lib/spreadsheet/format.rb
* to_xls gem example: http://stackoverflow.com/questions/15600987/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(initial_data, initial_preset = nil) ⇒ ListExport

Returns a new instance of ListExport.



24
25
26
27
28
29
30
# File 'app/models/list_export.rb', line 24

def initialize(initial_data, initial_preset = nil)
  @data = initial_data; @preset = initial_preset
  @csv_options =  { col_sep: ';', quote_char: '"' }
  raise_error_if_data_is_not_valid
  @data = processed_data
  @data = sorted_data
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(*args, &block) ⇒ Object (private)



279
280
281
# File 'app/models/list_export.rb', line 279

def method_missing(*args, &block)
  helpers.send(*args, &block)
end

Instance Attribute Details

#csv_optionsObject

Returns the value of attribute csv_options.



22
23
24
# File 'app/models/list_export.rb', line 22

def csv_options
  @csv_options
end

#dataObject

Returns the value of attribute data.



22
23
24
# File 'app/models/list_export.rb', line 22

def data
  @data
end

#presetObject

Returns the value of attribute preset.



22
23
24
# File 'app/models/list_export.rb', line 22

def preset
  @preset
end

Instance Method Details

#columnsObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/models/list_export.rb', line 32

def columns
  case preset.to_s
  when 'birthday_list'
    [:last_name, :first_name, :name_affix, :localized_birthday_this_year, 
      :localized_date_of_birth, :current_age]
  when 'address_list'
    #
    # TODO: Add the street as a separate column.
    # This was requested at the meeting at Gernsbach, Jun 2014.
    #
    [:last_name, :first_name, :name_affix, :postal_address_with_name_surrounding,
      :postal_address, :cached_localized_postal_address_updated_at, 
      :postal_address_postal_code, :postal_address_town,
      :postal_address_country, :postal_address_country_code,
      :personal_title, :address_label_text_above_name, :address_label_text_below_name,
      :address_label_text_before_name, :address_label_text_after_name]
  when 'phone_list'
    [:last_name, :first_name, :name_affix, :phone_label, :phone_number]
    # One row per phone number, not per user. See `#processed_data`.
  when 'email_list'
    [:last_name, :first_name, :name_affix, :email_label, :email_address]
    # One row per email, not per user. See `#processed_data`.
  when 'member_development'
    [:last_name, :first_name, :name_affix, :localized_date_of_birth, :date_of_death] + @leaf_group_names
  when 'join_statistics', 'join_and_persist_statistics'
    [:group] + ((Date.today.year - 25)..(Date.today.year)).to_a.reverse
  else
    # This name_list is the default.
    [:last_name, :first_name, :name_affix, :personal_title, :academic_degree]
  end
end

#headersObject



64
65
66
67
68
69
70
71
72
# File 'app/models/list_export.rb', line 64

def headers
  columns.collect do |column|
    if column.kind_of? Symbol
      I18n.translate column.to_s.gsub('cached_', '').gsub('localized_', '')
    else
      column
    end
  end
end

#processed_dataObject



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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'app/models/list_export.rb', line 74

def processed_data
  if preset.to_s.in?(['birthday_list', 'address_list', 'phone_list', 'email_list']) && @data.kind_of?(Group)
    # To be able to generate lists from Groups as well as search results, these presets expect 
    # an Array of Users as data. If a Group is given instead, just take the group members as data.
    #
    @data = @data.members
  end
  
  # Make the extended methods available that are defined below.
  #
  if @data.respond_to?(:first) && @data.first.kind_of?(User)
    @data = @data.collect { |user| user.becomes(ListExportUser) }
  end

  case preset.to_s
  when 'phone_list'
    #
    # For the phone_list, one row represents one phone number of a user,
    # not a user. I.e. there can be serveral rows per user.
    #
    data.collect { |user|
      user.phone_profile_fields.collect { |phone_field| {
        :last_name          => user.last_name,
        :first_name         => user.first_name,
        :name_affix         => user.name_affix,
        :phone_label        => phone_field.label,
        :phone_number       => phone_field.value
      } }
    }.flatten
  when 'email_list'
    #
    # For the email list, one row represents one email address of a user,
    # not a user. I.e. there can be several rows per user.
    #
    data.collect { |user|
      user.profile_fields.where(type: 'ProfileFieldTypes::Email').collect { |email_field| {
        :last_name          => user.last_name,
        :first_name         => user.first_name,
        :name_affix         => user.name_affix,
        :email_label        => email_field.label,
        :email_address      => email_field.value
      } }
    }.flatten
  when 'member_development'
    #
    # From data being a Group, this generates one line per user. Several columns are
    # created based on the leaf groups of the given Group.
    #
    @group = @data
    @group = @group.becomes(ListExportGroup)
    @leaf_groups = @group.leaf_groups
    # FIXME: The leaf groups should not return any officer group. Make this fix unneccessary:
    @leaf_groups -= @group.descendant_groups.where(name: ['officers', 'Amtsträger'])
    @leaf_group_names = @leaf_groups.collect { |group| group.name }
    @leaf_group_ids = @leaf_groups.collect { |group| group.id }
    # /FIXME - please uncomment:
    #@leaf_group_names = @leaf_groups.pluck(:name)
    #@leaf_group_ids = @leaf_groups.pluck(:id }
    
    @group.members.collect do |user|
      user = user.becomes(ListExportUser)
      row = {
        :last_name                      => user.last_name,
        :first_name                     => user.first_name,
        :name_affix                     => user.name_affix,
        :localized_date_of_birth        => user.localized_date_of_birth,
        :date_of_death                  => user.date_of_death
      }
      @leaf_groups.each do |leaf_group|
        membership = user.links_as_child_for_groups.where(ancestor_id: leaf_group.id).first
        date = membership.try(:valid_from).try(:to_date)
        localized_date = I18n.localize(date) if date
        row[leaf_group.name] = (localized_date || '')
      end
      row
    end
  when 'join_statistics', 'join_and_persist_statistics'
    #
    # From a list of groups, this creates one row per group.
    # The columns count the number of memberships valid from the year given by the column.
    # 
    # For the 'join_and_persist_statistics', only memberships are counted
    # that are still valid, i.e. still persist.
    #
    #            2014   2013   2012   2011   ...
    #  group1     24     22     25     28    ...
    #  group2     31     28     27     32    ...
    #   ...
    # 
    if @data.kind_of? Group
      @groups = @data.child_groups
    elsif @data.kind_of? Array
      @groups = @data
    end
    @groups.collect do |group|
      row = {}
      columns.each do |column|
        row[column] = if column.kind_of? Integer
          year = column 
          memberships = []
          if preset.to_s == 'join_statistics'
            memberships = UserGroupMembership.now_and_in_the_past.find_all_by_group(group)
          elsif preset.to_s == 'join_and_persist_statistics'
            memberships = UserGroupMembership.find_all_by_group(group)
          else
            raise 'attention, case not handled, yet!'
          end
          memberships = memberships.where(valid_from: "#{year}-01-01".to_datetime..("#{year + 1}-01-01".to_datetime - 1.second))
          memberships.count # TODO: Refactor this when allowing multiple dag links between two nodes.
        elsif column == :group
          group.name_with_corporation
        end
      end
      row
    end
  else
    data
  end
end

#raise_error_if_data_is_not_validObject



217
218
219
220
221
222
223
224
# File 'app/models/list_export.rb', line 217

def raise_error_if_data_is_not_valid
  case preset.to_s
  when 'birthday_list', 'address_list', 'phone_list', 'email_list', 'name_list'
    data.kind_of?(Group) || data.first.kind_of?(User) || raise("Expecing Group or list of Users as data in ListExport with the preset '#{preset}'.")
  when 'member_development'
    data.kind_of?(Group) || raise('The member_development list can only be generated for a Group, not an Array of Users.')
  end    
end

#sorted_dataObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/models/list_export.rb', line 194

def sorted_data
  case preset.to_s
  when 'birthday_list'
    data.sort_by do |user|
      user.date_of_birth.try(:strftime, "%m-%d") || ''
    end
  when 'address_list', 'name_list'
    data.sort_by do |user|
      user.last_name + user.first_name
    end
  when 'phone_list', 'email_list'
    data.sort_by do |user_hash|
      user_hash[:last_name] + user_hash[:first_name]
    end
  when 'join_statistics', 'join_and_persist_statistics'
    data.sort_by do |row|
      row.first
    end
  else
    data
  end
end

#to_aObject



266
267
268
# File 'app/models/list_export.rb', line 266

def to_a
  @data
end

#to_csvObject



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/list_export.rb', line 226

def to_csv
  CSV.generate(csv_options) do |csv|
    csv << headers
    data.each do |row|
      csv << columns.collect do |column_name|
        if row.respond_to? :values
          row[column_name]
        elsif row.respond_to? column_name
          row.try(:send, column_name) 
        else
          raise "Don't know how to access the given attribute or value. Trying to access '#{column_name}' on '#{row}'."
        end
      end
    end
  end
end

#to_htmlObject



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'app/models/list_export.rb', line 249

def to_html
  ("
    <table class='datatable joining statistics'>
      <thead>
        <tr>
          " + headers.collect { |header| "<th>#{header}</th>" }.join + "
        </tr>
      </thead>
      <tbody>
        " + data.collect { |row|
          "<tr>" + row.values.collect { |v| "<td>#{v}</td>" }.join + "</tr>"
        }.join + "
      </tbody>
    </table>
  ").html_safe
end

#to_sObject



270
271
272
# File 'app/models/list_export.rb', line 270

def to_s
  to_csv
end

#to_xlsObject



243
244
245
246
247
# File 'app/models/list_export.rb', line 243

def to_xls
  header_format = {weight: 'bold'}
  @data = @data.collect { |hash| HashWrapper.new(hash) } if @data.first.kind_of? Hash
  @data.to_xls(columns: columns, headers: headers, header_format: header_format)
end