Class: Interchange::Writer

Inherits:
Object
  • Object
show all
Includes:
ActionView::Helpers::TextHelper, Mixins::Logging
Defined in:
lib/interchange/Writer.rb

Overview

Public: Handles saving model data (exporting) to the local file system, be it intended for later imports back into Mockingjay, or for use as test data for UIAutomation.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#operation_resultsObject

A Hash of results from the last operation, containing keys:

:successful - Array of model instances whose export was successful.
:failures   - Array of model instances whose export was unsuccessful.


15
16
17
# File 'lib/interchange/Writer.rb', line 15

def operation_results
  @operation_results
end

Instance Method Details

#create_file(model, options = {}) ⇒ Object

Internal: Creates a local file that contains the provided JSON payload for the given model object. Model objects of a similar class are co-located in their own sub-directory.

model   - ActiveRecord::Base the root model to be exported.
options - Hash of options. (default: {})

Returns nothing.



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
# File 'lib/interchange/Writer.rb', line 145

def create_file(model, options={})
  Rails.logger.debug "Performing local export of #{model.class} model: #{model.short_title}"
  model.export_errors = []
  pretty_json = @rendering_controller.render_show_template_to_string(model, options)
  data_file_contents = decorate_output(model, pretty_json, options)
  base_path = options[:base_path] || 'public/interchange/'

  interchange_extension = 'js'
  interchange_type = if base_path == 'public/interchange/'
                       'export/'
                     else
                       ''
                     end

  # Determine directory we will write the file into:
  directory = "#{base_path}#{interchange_type}#{model.class.to_s.pluralize}"

  # Ensure the directory exists:
  directory_absolute_path = File.join(Rails.root, directory)
  Rails.logger.debug "Absolute directory path to write to: #{directory_absolute_path}"
  system("mkdir -p #{directory_absolute_path}") unless File.exists?(directory_absolute_path)

  # Write the file to disk:
  file_absolute_path = "#{directory_absolute_path}/#{model.interchange_name}.#{interchange_extension}"
  Rails.logger.debug "Absolute model file path to write to: #{file_absolute_path}"
  file = File.new(file_absolute_path, "w")
  file.write(data_file_contents)
  file.close
end

#decorate_output(model, pretty_json, options = {}) ⇒ Object

Internal: Decorates the passed in pretty JSON if needed. Wwe adorn the output with JavaScript that provides comments and variable instantiation.

model       - ActiveRecord::Base the root model being exported, from which we can get class 
              and name information to decorate with.
pretty_json - String with contents ready to be exported, that we'll decorate.
options     - Hash of options. (default: {})

Returns String of the decorated output.



130
131
132
133
134
135
# File 'lib/interchange/Writer.rb', line 130

def decorate_output(model, pretty_json, options={})
  decorated_output = "// #{model.interchange_name}.js\n"
  decorated_output.concat "var CERN#{model.interchange_type_name}_#{model.interchange_name} = #{pretty_json};"

  decorated_output
end

#export(model_class, options = {}) ⇒ Object

Public: This determines which specific objects will get exported, and then farms that out to the create_file() method to actually write to the file system.

We can handle multiple objects or a single object as well as regular exports and those meant for UIAutomation test data. Most of these options are specified in the optional options hash. In all cases, each call to this method is for dealing with objects of a specific model class.

model_class - Class of model to be exported.
options     - Hash of options that help us identify what subset of data (instances belonging to the model class)
              that we are meant to export. These options can include: 
              :ids      - what ID values to limit the export to (default: nil)
              :keywords - what keywords a model must have at least one of, in order to qualify for export (default: nil)


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
# File 'lib/interchange/Writer.rb', line 78

def export(model_class, options = {})
  ids = options[:ids]
  keywords = options[:keywords]
  base_path = options[:base_path]

  log "keywords sought: #{keywords}"
  
  controller_name = "Interchange::#{model_class.to_s.pluralize}Controller"
  controller = controller_name.constantize
  @rendering_controller = controller.new
  
  # Create dummy request and response objects on the rendering controller,
  # otherwise it will complain. This was gleaned from: http://stackoverflow.com/a/6773022/535054
  @rendering_controller.request = ActionDispatch::TestRequest.new
  @rendering_controller.response = ActionDispatch::TestResponse.new
  
  # Retrieve instances of the given model, and then iterate through them to do the export:
  models =
    if ids
      # We have a specific list of ID values we've been asked to export:
      model_class.where(:id => ids)
    elsif keywords
      # We want only those instances that have one of the list of keywords given to us:
      model_class.joins(:metadata_keywords).where(:metadata_keywords => {:text => keywords})
    else
      # We have no specific list of IDs, so we're going to export all instances:
      model_class.find(:all)
    end
  
  # Make the call to create_file() for each model instance we've determined needs to be exported:
  models.each do |model|
    begin
      # TODO: Ensure this is a top-level resource before exporting
      create_file model, options
      @operation_results[:successful] << model          
    rescue Exception => exception
      @operation_results[:failures] << model    
      model.export_errors << "Failure details: #{exception}"      
    end
  end
end

#mark_export_operationObject

Public: This demarcates a new export operation. You must provide a block for us to yield to when you call this method. That block should contain one or more calls to our export() method.

We reset the class variable ‘operation_results’ and populate it after yielding, with summary information about the export operation.

Returns nothing.



26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/interchange/Writer.rb', line 26

def mark_export_operation
  log "Starting Export operation."
  # Clear the class cached last-export summary:
  self.class.instance_variable_set(:@last_export_summary, nil)
  @operation_results = { successful: [], failures: [] }
  
  yield # Hand over control to the block that calls our export() method one or more times
  
  log "Completed Export operation."
  summarize_operation
  self.class.instance_variable_set(:@last_export_summary, @operation_results)
end

#summarize_operationObject

Public: This inspects the @operation_results Hash and adds more entries to it that describe and timestamp the operation. Included in this is a ‘flash_hash’ that a controller can use after the export operation, to present a one line summary.

Returns nothing.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/interchange/Writer.rb', line 45

def summarize_operation
  message = ''

  # Failures:
  if @operation_results[:failures].empty?
    flash_key = :notice
    @operation_results[:has_failures] = false
  else
    flash_key = :alert
    @operation_results[:has_failures] = true
    message = "#{@operation_results[:failures].count} #{@model_class.to_s} record(s) failed to export. "
  end

  # Summary Info:
  @operation_results[:total_records] = @operation_results[:failures].count + @operation_results[:successful].count
  message.concat(pluralize(@operation_results[:successful].count, "#{@model_class.to_s} record")).concat(' exported successfully. ')
  @operation_results[:flash_hash] = { flash_key => message }
  @operation_results[:timestamp] = Time.now      
end