initial commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
Dunlop workflow tool system
|
||||
================================
|
||||
|
||||
Installation
|
||||
---------------------------------------
|
||||
To use Dunlop in your project add it to your Gemfile
|
||||
```ruby
|
||||
gem 'dunlop'
|
||||
```
|
||||
and create the project scaffold:
|
||||
```
|
||||
rails generate dunlop:install
|
||||
```
|
||||
|
||||
Generators
|
||||
---------------------------------------
|
||||
|
||||
### source_file
|
||||
To add a source file to your project use the <tt>source_file</tt>
|
||||
generator:
|
||||
```
|
||||
rails generate source_file planned_dslams
|
||||
```
|
||||
<strong>NOTE: do a git commit before you use a generator!</strong>
|
||||
Possible options for the generator are:
|
||||
|
||||
* `--sql` to add scaffold sql templates to load data using sql
|
||||
* `--source_record` if the data is loaded into a connected SourceRecord
|
||||
model associated with the latest loaded source file.
|
||||
* `--attributes="Attr 1" "Attr 2" attr_3` to list the header columns of
|
||||
the source file. The attributes will be assumed to have an underscored
|
||||
column name form with spaces replaced by underscores. Default column
|
||||
type will be string. It is meant as a fast and good start, not a final
|
||||
result.
|
||||
|
||||
So to generate a full featured source file the following will work:
|
||||
```
|
||||
rails generate source_file planned_dslams --sql --source_record --attributes="Attr 1" attr_etc
|
||||
```
|
||||
|
||||
### dunlop:scenario
|
||||
To add a scenario to your project:
|
||||
```
|
||||
rails generate dunlop:scenario VulaMigration
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
require "dunlop"
|
||||
@@ -0,0 +1,123 @@
|
||||
require "csv"
|
||||
module Dunlop::CsvBuilder
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attr_reader :resource, :options, :csv_options, :controller
|
||||
class_attribute :default_csv_options
|
||||
self.default_csv_options = {}
|
||||
end
|
||||
|
||||
def initialize(resource, options = {})
|
||||
@resource, @options = resource, options
|
||||
@csv_options = default_csv_options.merge(options.slice(:col_sep, :row_sep, :encoding, :field_size_limit, :quote_char, :force_quotes))
|
||||
@controller = options[:controller]
|
||||
end
|
||||
|
||||
def file_name
|
||||
options[:file_name] || "#{Rails.application.config.application_name}-#{date_number}-data.csv"
|
||||
end
|
||||
|
||||
def headers
|
||||
[]
|
||||
end
|
||||
|
||||
def for_each_serialized_row(&block)
|
||||
iterate_over = iteration_resource
|
||||
iterate_over = Array.wrap(iterate_over) unless iterate_over.respond_to?(:find_each_ordered) || iterate_over.respond_to?(:each) # allow resource to be a record using the index's csv builder
|
||||
iteration_method = iterate_over.respond_to?(:find_each_ordered) ? :find_each_ordered : :each
|
||||
iterate_over.public_send(iteration_method) do |*args|
|
||||
block.call serialize_row(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def serialize_row(record)
|
||||
[record.id]
|
||||
end
|
||||
|
||||
def data
|
||||
# use #to_csv for base_class builders if present
|
||||
# in custom class implement"
|
||||
# def data
|
||||
# resource.to_csv
|
||||
# end
|
||||
# since Arrays implement #to_csv, but we do not want to use it in this case we explicitly exclude it
|
||||
return resource.to_csv if self.class == CsvBuilder and resource.present? and resource.respond_to?(:to_csv) and !resource.is_a?(Array)
|
||||
|
||||
# try iteration otherwise
|
||||
str = CSV.generate csv_options do |csv|
|
||||
csv << headers if headers.present?
|
||||
for_each_serialized_row do |serialized_row|
|
||||
csv << serialized_row
|
||||
end
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
def date_number
|
||||
Date.current.to_s(:number)
|
||||
end
|
||||
|
||||
# This method can be overridden in order to use another iteration source than the resource eg:
|
||||
# resouce = batch
|
||||
# def iteration_resource
|
||||
# resource.workflow_instances
|
||||
# end
|
||||
def iteration_resource
|
||||
resource
|
||||
end
|
||||
|
||||
#TODO: this is a separate include, not part of the core CsvBuilder object
|
||||
def workflow_instances_in_batch
|
||||
resource.workflow_instances.for_filtered_export
|
||||
end
|
||||
|
||||
# Do a lookup on a record of an attribute and map the value to the
|
||||
# csv required version if appliccable. At the moment booleans are
|
||||
# converted to zeros and ones
|
||||
def record_attribute(record, attribute_name)
|
||||
result = record.public_send(attribute_name)
|
||||
case result
|
||||
when TrueClass then 1
|
||||
when FalseClass then 0
|
||||
when String then result.strip.gsub(/\r\n?|\n/, " ")
|
||||
when DayTimeBase then result.number
|
||||
else result
|
||||
end
|
||||
end
|
||||
|
||||
# Make some options available as methods
|
||||
%i[action_name controller_path].each do |option_method|
|
||||
define_method option_method do
|
||||
options[option_method]
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# find the csv builder for the given route spec
|
||||
def find_for_route(route_spec)
|
||||
find_for_route_lookup_order(route_spec).lazy.map(&:safe_constantize).find(&:itself)
|
||||
end
|
||||
|
||||
# separate method since this is the important method to test, so extracted from the main method
|
||||
def find_for_route_lookup_order(route_spec)
|
||||
controller_path, action_name = route_spec.split('#')
|
||||
|
||||
controller_parts = controller_path.split('/')
|
||||
if controller_parts.size < 2
|
||||
parts = controller_parts.map(&:classify)
|
||||
else
|
||||
parts = controller_parts[0..-2].map(&:camelize)
|
||||
parts << controller_parts.last.classify
|
||||
end
|
||||
|
||||
# Create a lookup tree that uses all the namespaces, followed by the actoin name
|
||||
# and ending with CsvBuilder for consitancy. Aka: if the route is:
|
||||
# workflow_instances/scenario1s#show
|
||||
# the lookup_order should be:
|
||||
# ["WorkflowInstanceBatch::Scenario1::ShowCsvBuilder", "WorkflowInstanceBatch::ShowCsvBuilder"]
|
||||
action_postfix = "::#{action_name.camelize}CsvBuilder"
|
||||
parts.map.with_index{|p, i| parts[0..(i - 1)].join('::') + action_postfix}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
# Asset groups
|
||||
if (Rails.groups.map(&:to_s) & %w[development assets test]).any?
|
||||
require "font-awesome-rails"
|
||||
require "sass-rails"
|
||||
require "js-routes"
|
||||
require "momentjs-rails"
|
||||
require 'coffee-rails'
|
||||
require "pickadate-rails"
|
||||
end
|
||||
|
||||
require "slim-rails"
|
||||
require 'record_collection'
|
||||
require 'request_store'
|
||||
require 'ransack'
|
||||
require 'devise'
|
||||
require 'devise-token_authenticatable'
|
||||
require 'state_machines-activerecord'
|
||||
require 'carrierwave'
|
||||
require 'simple_form'
|
||||
require 'redcarpet'
|
||||
require 'exception_notification'
|
||||
require 'facets/enumerable/map_send'
|
||||
require 'memoist'
|
||||
require 'cancancan'
|
||||
|
||||
if Rails::VERSION::MAJOR < 5
|
||||
ActiveRecord::Associations::Builder::Association.valid_options |= [:optional]
|
||||
class ActiveRecord::Migration
|
||||
# rails5 compatibility
|
||||
def self.[](*)
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Dunlop
|
||||
class Engine < ::Rails::Engine
|
||||
isolate_namespace Dunlop
|
||||
initializer 'dunlop.action_controller' do |app|
|
||||
ActiveSupport.on_load :action_controller do
|
||||
helper Dunlop::ApplicationHelper if respond_to?(:helper)
|
||||
end
|
||||
end
|
||||
|
||||
initializer "dunlop.ability", group: 'dunlop' do |app|
|
||||
if ability_class = "Ability".safe_constantize and ability_class.respond_to?(:add_dunlop_allowed_authorization_classes!)
|
||||
ability_class.add_dunlop_allowed_authorization_classes!
|
||||
end
|
||||
end
|
||||
|
||||
# add migrations to containing application
|
||||
initializer :append_migrations do |app|
|
||||
unless app.root.to_s.match root.to_s
|
||||
config.paths["db/migrate"].expanded.each do |expanded_path|
|
||||
app.config.paths["db/migrate"] << expanded_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
module Dunlop::Generators
|
||||
|
||||
end
|
||||
|
||||
module Dunlop::Install
|
||||
end
|
||||
|
||||
require "dunlop/generators/generator_helpers"
|
||||
@@ -0,0 +1,104 @@
|
||||
module Dunlop::Generators::GeneratorHelpers
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def inject_before_last_end(file_path, code)
|
||||
code << "\n" unless code.last == "\n"
|
||||
code = " #{code}" unless code.first =~ /^\s/
|
||||
inject_into_file file_path, code, before: /^end/
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def next_migration_number(path)
|
||||
ActiveRecord::Generators::Base.next_migration_number(path)
|
||||
end
|
||||
end
|
||||
|
||||
class DunlopGeneratorAttribute
|
||||
attr_reader :name
|
||||
def initialize(spec)
|
||||
@name, @type = spec.to_s.strip.split(':')
|
||||
end
|
||||
|
||||
def type
|
||||
@type || 'string'
|
||||
end
|
||||
|
||||
def column_name
|
||||
name.to_s.underscore.gsub(/\W/, '_')
|
||||
end
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
def form_type
|
||||
case type
|
||||
when 'boolean' then 'boolean'
|
||||
else 'input'
|
||||
end
|
||||
end
|
||||
|
||||
def boolean?
|
||||
type == 'boolean'
|
||||
end
|
||||
|
||||
def integer?
|
||||
type == 'integer'
|
||||
end
|
||||
|
||||
def float?
|
||||
%w[decimal float].include?(type)
|
||||
end
|
||||
|
||||
def numeric?
|
||||
integer? or float?
|
||||
end
|
||||
|
||||
def date?
|
||||
type == 'date'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attributes
|
||||
options.attributes.map{|a| DunlopGeneratorAttribute.new(a) }
|
||||
end
|
||||
|
||||
# Taken from base rails and modified for gsub block support
|
||||
def gsub_file(path, flag, *args, &block)
|
||||
config = args.last.is_a?(Hash) ? args.pop : {}
|
||||
|
||||
path = File.expand_path(path, destination_root)
|
||||
if not File.exist? path
|
||||
say_status :gsub, set_color("The file #{path} cannot be found", :yellow)
|
||||
return
|
||||
end
|
||||
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
||||
|
||||
unless options[:pretend]
|
||||
content = File.binread(path)
|
||||
if block.present? # modification from rails standard to support match data in supplied blocks
|
||||
content.gsub!(flag, *args){ block.call( Regexp.last_match ) }
|
||||
else
|
||||
content.gsub!(flag, *args)
|
||||
end
|
||||
File.open(path, "wb") { |file| file.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Add a name to a definition list. eg:
|
||||
# setup_secnarios %i[
|
||||
# existing_scenario
|
||||
# ]
|
||||
# at the bottom.
|
||||
def add_to_symbol_list(file_path, setup_method, name)
|
||||
if behavior == :revoke
|
||||
gsub_file file_path, /(#{setup_method} %i\[.*)\n\s+#{name}(.*)\]/m, '\1\2]'
|
||||
else
|
||||
return if File.read(file_path) =~ /^ #{setup_method} %i\[[^\]]*\W#{name}\W[^\]]*\]/m # only add once
|
||||
gsub_file file_path, /(^ #{setup_method} %i\[\n[^\]]*)]/m, "\\1 #{name}\n ]"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module Dunlop
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
module Dunlop
|
||||
VERSION = "1.4.0"
|
||||
end
|
||||
Reference in New Issue
Block a user