PageRenderTime 141ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/to_csv/csv_converter.rb

https://github.com/cicloid/to-csv
Ruby | 237 lines | 200 code | 36 blank | 1 comment | 36 complexity | f1d1208aee75488dcbe0ff6cacea9db8 MD5 | raw file
  1. # encoding: UTF-8
  2. module ToCSV
  3. CSVClass = RUBY_VERSION < '1.9' ? ::FasterCSV : ::CSV
  4. class Converter
  5. def initialize(data, options = {}, csv_options = {}, &block)
  6. @opts = options.to_options.reverse_merge({
  7. :byte_order_marker => ToCSV.byte_order_marker,
  8. :locale => ToCSV.locale || ::I18n.locale,
  9. :pkey => ToCSV.pkey,
  10. :timestamps => ToCSV.timestamps
  11. })
  12. @opts[:only] = Array(@opts[:only]).map(&:to_s)
  13. @opts[:except] = Array(@opts[:except]).map(&:to_s)
  14. @opts[:methods] = Array(@opts[:methods]).map(&:to_s)
  15. @data = data
  16. @block = block
  17. @csv_options = csv_options.to_options.reverse_merge(ToCSV.csv_options)
  18. @associations = @opts[:include].kind_of?(Array) ? @opts[:include] : [@opts[:include]] if @opts[:include]
  19. end
  20. def to_csv
  21. build_headers_and_rows
  22. output = CSVClass.generate(@csv_options) do |csv|
  23. csv << @header_row if @header_row.try(:any?)
  24. @rows.each { |row| csv << row }
  25. end
  26. @opts[:byte_order_marker] ? "\xEF\xBB\xBF#{output}" : output
  27. end
  28. private
  29. def build_headers_and_rows
  30. send "headers_and_rows_from_#{ discover_data_type }"
  31. end
  32. def discover_data_type
  33. test_data = @data.first
  34. return 'ar_object' if instance_of_active_record? test_data
  35. return 'hash' if test_data.is_a? Hash
  36. return 'unidimensional_array' if test_data.is_a?(Array) && !test_data.first.is_a?(Array)
  37. return 'bidimensional_array' if test_data.is_a?(Array) && test_data.first.is_a?(Array) && test_data.first.size == 2
  38. 'simple_data'
  39. end
  40. def instance_of_active_record?(obj)
  41. obj.class.base_class.superclass == ActiveRecord::Base
  42. rescue Exception
  43. false
  44. end
  45. def headers_and_rows_from_simple_data
  46. @header_row = nil
  47. @rows = [@data.dup]
  48. end
  49. def headers_and_rows_from_hash
  50. @header_row = @data.first.keys if display_headers?
  51. @rows = @data.map(&:values)
  52. end
  53. def headers_and_rows_from_unidimensional_array
  54. @header_row = @data.first if display_headers?
  55. @rows = @data[1..-1]
  56. end
  57. def headers_and_rows_from_bidimensional_array
  58. @header_row = @data.first.map(&:first) if display_headers?
  59. @rows = @data.map { |array| array.map(&:last) }
  60. end
  61. def headers_and_rows_from_ar_object
  62. attributes = sort_attributes(filter_attributes(attribute_names))
  63. if display_headers?
  64. @header_row = human_attribute_names(attributes)
  65. @header_row |= human_attribute_names(headers_from_association_ar_object) if @opts[:include]
  66. end
  67. @rows = if @block
  68. attributes_class = Struct.new(*attributes.map(&:to_sym))
  69. @data.map do |item|
  70. @block.call(obj = attributes_class.new, item)
  71. result = attributes.map { |attribute| obj[attribute] || try_formatting_date(item.send(attribute)) }
  72. (result << rows_from_association_ar_object(item);result.flatten!) if @opts[:include] && @row_header_association
  73. result
  74. end
  75. else
  76. @data.map do |item|
  77. result = attributes.map { |attribute| try_formatting_date item.send(attribute) }
  78. (result << rows_from_association_ar_object(item);result.flatten!) if @opts[:include] && @row_header_association
  79. result
  80. end
  81. end
  82. end
  83. def rows_from_association_ar_object(item)
  84. @result = []
  85. @associations.each do |association|
  86. @row_header_association = instance_variable_get("@row_header_association_for_#{from_sym_to_string(association)}")
  87. association = association.to_sym
  88. case item.class.reflect_on_association(association).macro
  89. when :has_many, :has_and_belongs_to_many
  90. records = item.send(association).to_a
  91. unless records.empty?
  92. records.collect do |record|
  93. @row_header_association.map do |attribute|
  94. @result << try_formatting_date(record.send(attribute))
  95. end
  96. end
  97. end
  98. when :has_one, :belongs_to
  99. if record = item.send(association)
  100. @row_header_association.map do |attribute|
  101. @result << try_formatting_date(record.send(attribute))
  102. end
  103. end
  104. end
  105. end
  106. @result.flatten
  107. end
  108. def headers_from_association_ar_object
  109. @header_association = []
  110. @associations.each do |association|
  111. association = association.to_sym
  112. case @data.first.class.reflect_on_association(association).macro
  113. when :has_many, :has_and_belongs_to_many
  114. records = @data.first.send(association).to_a
  115. unless records.empty?
  116. instance_variable_set("@row_header_association_for_#{from_sym_to_string(association)}", records.first.attribute_names.map(&:to_s))
  117. @row_header_association = instance_variable_get("@row_header_association_for_#{from_sym_to_string(association)}")
  118. (1..records.size).each do |record|
  119. @row_header_association.map do |header|
  120. @header_association << "#{from_sym_to_string(association)}_#{record}_#{header}"
  121. end
  122. end
  123. end
  124. when :has_one, :belongs_to
  125. instance_variable_set("@row_header_association_for_#{from_sym_to_string(association)}", @data.first.send(association).attribute_names.map(&:to_s))
  126. @row_header_association = instance_variable_get("@row_header_association_for_#{from_sym_to_string(association)}")
  127. @data.first.send(association).attribute_names.map do |header|
  128. @header_association << "#{from_sym_to_string(association)}_#{header}"
  129. end
  130. end
  131. end
  132. @header_association.flatten
  133. end
  134. def display_headers?
  135. @opts[:headers].nil? || (Array(@opts[:headers]).any? && Array(@opts[:headers]).all? { |h| h != false })
  136. end
  137. def human_attribute_names(attributes)
  138. @opts[:locale] ? translate(attributes) : humanize(attributes)
  139. end
  140. def humanize(attributes)
  141. attributes.map(&:humanize)
  142. end
  143. def translate(attributes)
  144. ::I18n.with_options :locale => @opts[:locale], :scope => [:activerecord, :attributes, @data.first.class.to_s.underscore] do |locale|
  145. attributes.map { |attribute| locale.t(attribute, :default => attribute.humanize) }
  146. end
  147. end
  148. def try_formatting_date(value)
  149. is_a_date?(value) ? value.to_s(:db) : value
  150. end
  151. def is_a_date?(value)
  152. value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
  153. end
  154. def pkey_filter(attributes)
  155. return attributes if @opts[:pkey]
  156. attributes - Array(@data.first.class.pkey.to_s)
  157. end
  158. def timestamps_filter(attributes)
  159. return attributes if @opts[:timestamps]
  160. return attributes if (@opts[:only] + @opts[:except]).any? { |attribute| timestamps.include? attribute }
  161. attributes - timestamps
  162. end
  163. def timestamps
  164. @timestamps ||= %w[ created_at updated_at created_on updated_on ]
  165. end
  166. def methods_filter(attributes)
  167. attributes | @opts[:methods]
  168. end
  169. def only_filter(attributes)
  170. return attributes if @opts[:only].empty?
  171. attributes & @opts[:only]
  172. end
  173. def except_filter(attributes)
  174. attributes - @opts[:except]
  175. end
  176. def attribute_names
  177. @data.first.attribute_names.map(&:to_s)
  178. end
  179. def filter_attributes(attributes)
  180. attributes = methods_filter(attributes)
  181. attributes = pkey_filter(attributes)
  182. attributes = timestamps_filter(attributes)
  183. attributes = @opts[:only].any?? only_filter(attributes) : except_filter(attributes)
  184. attributes
  185. end
  186. def sort_attributes(attributes)
  187. attributes = attributes.map(&:to_s).sort
  188. return attributes if @opts[:headers].nil?
  189. headers = Array(@opts[:headers]).map(&:to_s)
  190. headers.delete_if { |attribute| attribute == 'false' }
  191. if index = headers.index('all')
  192. (headers & attributes).insert(index, (attributes - headers)).flatten
  193. else
  194. headers + (attributes - headers)
  195. end
  196. end
  197. def from_sym_to_string(sym)
  198. sym.to_s.gsub(':','')
  199. end
  200. end
  201. end