mobile_workflow 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -0
  3. data/app/controllers/concerns/mobile_workflow/s3_storable.rb +7 -5
  4. data/app/controllers/mobile_workflow/sns_notifications_controller.rb +14 -37
  5. data/app/jobs/mobile_workflow/add_attachment_job.rb +30 -0
  6. data/app/models/concerns/mobile_workflow/attachable.rb +32 -0
  7. data/app/models/concerns/mobile_workflow/displayable/steps/form.rb +70 -0
  8. data/app/models/concerns/mobile_workflow/displayable/steps/list.rb +15 -0
  9. data/app/models/concerns/mobile_workflow/displayable/steps/map.rb +11 -0
  10. data/app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb +11 -0
  11. data/app/models/concerns/mobile_workflow/displayable/steps/question.rb +25 -0
  12. data/app/models/concerns/mobile_workflow/displayable/steps/stack.rb +75 -0
  13. data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb +30 -0
  14. data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb +40 -0
  15. data/app/models/concerns/mobile_workflow/displayable.rb +17 -146
  16. data/lib/generators/mobile_workflow/install/install_generator.rb +12 -0
  17. data/lib/generators/mobile_workflow/install/templates/app/models/application_record.rb +1 -0
  18. data/lib/generators/mobile_workflow/install/templates/deserializer.rb.erb +13 -0
  19. data/lib/generators/mobile_workflow/install/templates/deserializer_spec.rb.erb +29 -0
  20. data/lib/generators/mobile_workflow/install/templates/user.rb.erb +4 -0
  21. data/lib/generators/mobile_workflow/model_generator.rb +9 -2
  22. data/lib/generators/mobile_workflow/templates/controller.rb.erb +4 -2
  23. data/lib/generators/mobile_workflow/templates/model.rb.erb +17 -2
  24. data/lib/mobile_workflow/displayable.rb +9 -0
  25. data/lib/mobile_workflow/engine.rb +7 -5
  26. data/lib/mobile_workflow/version.rb +1 -1
  27. data/lib/mobile_workflow.rb +1 -0
  28. metadata +49 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d2de8e8e58abbf48c61361f6e1f29743fb4503ea9903c2b8a262e1e8c5419f0
4
- data.tar.gz: 7f4021ded5e08c6f4f1a66cc69556c47d6dc57b6f099c206dc9bdb3f892a7135
3
+ metadata.gz: cf9a3d0ae2752b171aea1043b7cc57a8425b80ecf086bb226faadffb936fd5ee
4
+ data.tar.gz: 1d0c4bf77cbbb66844a58a4d94bfb5532f4706a4a5fa2ae02f01e1e0f72e4939
5
5
  SHA512:
6
- metadata.gz: 071d242cfba279223971ff929acaba7285e017b5c836be68ba5f02ad46a4571d39c98ea25de67d4b6ea586b4bdded534f6fae1cac27ebc53e3f8ba642ec01c33
7
- data.tar.gz: 8d4ce06b17e4b3d4cd327c5d65f6035065e098456e39d10e420f287b5c545d8524899870c00bb893587ef4c4c72587e98193b8fdf78dde74671f32d0cf0d7083
6
+ metadata.gz: e3c1748bdbe2b84415e434632080968cc02fb4e6e7b5a248bf8d979868b74714ce89c0d04165207d3b67008e45b3f08e4b84f67124a128f971627ad28611591f
7
+ data.tar.gz: 90329d6e78a89c601fa6bb5cc62b4d856b9ba264e56d6bfe83b7e1afc5ea5d1849355ccf1beb272d2474c90b5b5ec2bca191903db9dbe465fa5141b52053dadf
data/README.md CHANGED
@@ -26,6 +26,89 @@ Or install it yourself as:
26
26
  $ gem install mobile_workflow
27
27
  ```
28
28
 
29
+ ## Upgrade to version 0.7.7 or higher
30
+ The following utility methods have changed their parameters:
31
+
32
+ ```ruby
33
+ # app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb#20
34
+ def mw_grid_item(id: self.id, text:, detail_text: nil, preview_url: nil)
35
+ raise 'Missing id' if id.nil?
36
+ raise 'Missing text' if text.nil?
37
+
38
+ { id: id, text: text, type: :item, detailText: detail_text, imageURL: preview_url }.compact
39
+ end
40
+
41
+ # app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb#20
42
+ def mw_stack_list_item(id:, text:, detail_text: nil, preview_url: nil)
43
+ raise 'Missing id' if id.nil?
44
+ raise 'Missing text' if text.nil?
45
+
46
+ { id: id.to_s, text: text, detailText: detail_text, type: :listItem, imageURL: preview_url }.compact
47
+ end
48
+
49
+ # app/models/concerns/mobile_workflow/displayable/steps/list.rb#5
50
+ def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, preview_url: nil)
51
+ { id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name, imageURL: preview_url }.compact
52
+ end
53
+
54
+ # app/models/concerns/mobile_workflow/displayable/steps/stack.rb#26
55
+ def mw_display_video(preview_url:, attachment_url:)
56
+ {type: :video, previewURL: preview_url, url: attachment_url}
57
+ end
58
+
59
+ # app/models/concerns/mobile_workflow/displayable/steps/stack.rb#11
60
+ def mw_display_image(preview_url:, attachment_url:, content_mode: :scale_aspect_fill)
61
+ validate_content_mode!(content_mode)
62
+
63
+ {type: :image, contentMode: camelcase_converter(content_mode.to_s, first_letter: :lower), previewURL: preview_url, url: attachment_url}
64
+ end
65
+ ```
66
+
67
+ All URLs MUST now be explicitly sent as arguments to the above methods, which means they must be previously set. If not, the methods will not work.
68
+
69
+ In order to support projects using `ActiveStorage`, there is a new model concern `MobileWorkflow::Attachable` that provides a few helpers. This is what you can do to upgrade if you use ActiveStorage (otherwise the helpers must be manually created):
70
+
71
+ 1. Include the concern in the `ApplicationRecord` class, together with `MobileWorkflow::Displayable`:
72
+
73
+ ```ruby
74
+ class ApplicationRecord < ActiveRecord::Base
75
+ include MobileWorkflow::Attachable
76
+ include MobileWorkflow::Displayable
77
+ end
78
+ ```
79
+
80
+ 2. Once included, the following helpers will be available, so use them to generate the intended URLs:
81
+
82
+ ```ruby
83
+ def preview_url(attachment, options: { resize_to_fill: [200, 200] })
84
+ return nil unless attachment.attached?
85
+
86
+ if attachment.image?
87
+ rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
88
+ elsif attachment.previewable?
89
+ rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
90
+ else
91
+ return nil
92
+ end
93
+ end
94
+
95
+ def attachment_url(attachment)
96
+ return nil unless attachment.attached?
97
+
98
+ rails_blob_url(attachment, host: heroku_attachment_host)
99
+ end
100
+ ```
101
+
102
+ Example of use:
103
+ ```ruby
104
+ # Old method call
105
+ mw_list_item(text: 'John Doe', detail_text: 'Company Name', image_attachment: <ActiveStorage::Attached::One>, image_url: 'https://test.org/preview')
106
+
107
+ # New method call
108
+ preview_url = preview_url(<ActiveStorage::Attached::One>, options: { resize_to_fill: [200, 200] }) || 'https://test.org/preview'
109
+ mw_list_item(text: 'John Doe', detail_text: 'Company Name', preview_url: preview_url)
110
+ ```
111
+
29
112
  ## Contributing
30
113
  Contribution directions go here.
31
114
 
@@ -1,3 +1,5 @@
1
+ require 'aws-sdk-s3'
2
+
1
3
  module MobileWorkflow
2
4
  module S3Storable
3
5
  if Object.const_defined?("Aws::S3")
@@ -10,10 +12,11 @@ module MobileWorkflow
10
12
  identifier = binary[:identifier]
11
13
  object_attribute = identifier.split(".")[0] # ensure extension doesnt get added here
12
14
  extension = binary[:mimetype].split('/')[1] # i.e. image/jpg --> jpg, video/mp4 --> mp4
13
-
15
+ metadata = binary[:metadata]
16
+
14
17
  {
15
18
  identifier: identifier,
16
- url: presigned_url("#{object.class.name.underscore}/#{object.id}/#{object_attribute}/#{s3_object_uuid}.#{extension}"),
19
+ url: presigned_url("#{object.class.name.underscore}/#{object.id}/#{object_attribute}/#{s3_object_uuid}.#{extension}", metadata: metadata),
17
20
  method: "PUT"
18
21
  }
19
22
  end
@@ -24,8 +27,8 @@ module MobileWorkflow
24
27
  SecureRandom.uuid
25
28
  end
26
29
 
27
- def presigned_url(key)
28
- presigner.presigned_url(:put_object, bucket: ENV['AWS_BUCKET_NAME'], key: key, metadata: {})
30
+ def presigned_url(key, metadata: nil)
31
+ presigner.presigned_url(:put_object, bucket: ENV['AWS_BUCKET_NAME'], key: key, metadata: metadata)
29
32
  end
30
33
 
31
34
  def presigner
@@ -35,7 +38,6 @@ module MobileWorkflow
35
38
  def s3_client
36
39
  Aws::S3::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
37
40
  end
38
-
39
41
  end
40
42
  end
41
43
  end
@@ -1,3 +1,5 @@
1
+ require 'aws-sdk-sns'
2
+
1
3
  module MobileWorkflow
2
4
  class SnsNotificationsController < ActionController::API
3
5
  if Object.const_defined?("Aws::S3") && Object.const_defined?("Aws::SNS")
@@ -10,26 +12,16 @@ module MobileWorkflow
10
12
  when 'SubscriptionConfirmation'
11
13
  confirm_subscription ? (head :ok) : (head :bad_request)
12
14
  else
13
- add_attachment
15
+ AddAttachmentJob.perform_now(object, object_key, attribute_name)
16
+ head :ok
14
17
  end
18
+ rescue NameError => e
19
+ Rails.logger.warn "Error attaching object: #{e.message}"
20
+ rescue ActiveRecord::RecordNotFound
21
+ head :not_found
15
22
  end
16
23
 
17
24
  private
18
- def add_attachment
19
- begin
20
- @object = find_object
21
- @object.send("#{attribute_name}=",active_record_blob)
22
- if @object.save
23
- head :ok
24
- else
25
- Rails.logger.warn "Error saving object: #{@object} #{object.errors.full_messages}"
26
- head :unprocessable_entity
27
- end
28
- rescue NameError => e
29
- Rails.logger.warn "Error attaching object: #{e.message}"
30
- head :unprocessable_entity
31
- end
32
- end
33
25
 
34
26
  def verify_request_authenticity
35
27
  head :unauthorized if raw_post.blank?
@@ -37,22 +29,11 @@ module MobileWorkflow
37
29
  #head :unauthorized if raw_post.blank? || !message_verifier.authentic?(raw_post) # Not working
38
30
  end
39
31
 
40
- def active_record_blob
41
- s3_object = s3_bucket.object(object_key)
42
- checksum_base64 = checksum_base64(object_key, s3_object)
43
- ActiveStorage::Blob.create! key: s3_object.key, filename: s3_object.key, byte_size: s3_object.size, checksum: checksum_base64, content_type: s3_object.content_type
44
- end
45
-
46
- def checksum_base64(object_key, s3_object)
47
- path = Tempfile.new(object_key).path
48
- s3_object.download_file(path)
49
- file = File.new(path)
50
- Digest::MD5.file(file).base64digest
51
- end
52
-
53
- def find_object
54
- object_class_name, object_id = key_identifiers
55
- object_class_name.camelcase.constantize.find(object_id.to_i)
32
+ def object
33
+ @object ||= begin
34
+ object_class_name, object_id = key_identifiers
35
+ object_class_name.camelcase.constantize.find(object_id.to_i)
36
+ end
56
37
  end
57
38
 
58
39
  def attribute_name
@@ -91,11 +72,7 @@ module MobileWorkflow
91
72
  Rails.logger.warn(e.message)
92
73
  return false
93
74
  end
94
-
95
- def s3_bucket
96
- Aws::S3::Resource.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY']).bucket(ENV['AWS_BUCKET_NAME'])
97
- end
98
-
75
+
99
76
  def sns_client
100
77
  Aws::SNS::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
101
78
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MobileWorkflow
4
+ class AddAttachmentJob < ApplicationJob
5
+ def perform(object, object_key, attribute_name)
6
+ object.send("#{attribute_name}=", active_record_blob_from_s3(object_key))
7
+ Rails.logger.warn "Error saving object: #{object} #{object.errors.full_messages}" unless object.save
8
+ rescue NoMethodError => e
9
+ Rails.logger.warn "Error attaching object: #{e.message}"
10
+ end
11
+
12
+ private
13
+
14
+ def active_record_blob_from_s3(object_key)
15
+ # etag cannot be used as the MD5 checksum when doing multi-part uploads
16
+ s3_object = s3_bucket.object(object_key)
17
+ base64_digest = hex_to_base64_digest(s3_object.etag.delete('"'))
18
+ ActiveStorage::Blob.create! key: s3_object.key, filename: s3_object.key, byte_size: s3_object.size,
19
+ checksum: base64_digest, content_type: s3_object.content_type
20
+ end
21
+
22
+ def s3_bucket
23
+ Aws::S3::Resource.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY']).bucket(ENV['AWS_BUCKET_NAME'])
24
+ end
25
+
26
+ def hex_to_base64_digest(hexdigest)
27
+ [[hexdigest].pack('H*')].pack('m0')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module MobileWorkflow
2
+ module Attachable
3
+ extend ActiveSupport::Concern
4
+ include Rails.application.routes.url_helpers
5
+
6
+ def preview_url(attachment, options: { resize_to_fill: [200, 200] })
7
+ return nil unless attachment.attached?
8
+
9
+ if attachment.image?
10
+ rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
11
+ elsif attachment.previewable?
12
+ rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
13
+ else
14
+ return nil
15
+ end
16
+ end
17
+
18
+ def attachment_url(attachment)
19
+ return nil unless attachment.attached?
20
+
21
+ rails_blob_url(attachment, host: heroku_attachment_host)
22
+ end
23
+
24
+ private
25
+
26
+ def heroku_attachment_host
27
+ # TODO: MBS - move this to a configuration property
28
+ app_name = Rails.env.test? ? 'test-app' : ENV.fetch('HEROKU_APP_NAME')
29
+ "https://#{app_name}.herokuapp.com"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Form
5
+ def mw_form_section(label:, identifier:)
6
+ raise 'Missing label' if label.nil?
7
+ raise 'Missing identifier' if identifier.nil?
8
+
9
+ { item_type: :section, label: label, identifier: identifier }
10
+ end
11
+
12
+ def mw_form_multiple_selection(label:, identifier:, multiple_selection_options:, selection_type: :single, optional: false, show_other_option: false)
13
+ raise 'Missing label' if label.nil?
14
+ raise 'Missing identifier' if identifier.nil?
15
+ raise 'Missing multiple selection options' if multiple_selection_options.nil?
16
+
17
+ { item_type: :multiple_selection, label: label, identifier: identifier, multiple_selection_options: multiple_selection_options, selection_type: selection_type, optional: optional, show_other_option: show_other_option }
18
+ end
19
+
20
+ def mw_form_multiple_selection_options(text:, hint: nil, is_pre_selected: false)
21
+ raise 'Missing text' if text.nil?
22
+
23
+ { text: text, hint: hint, isPreSelected: is_pre_selected }
24
+ end
25
+
26
+ def mw_form_number(label:, identifier:, placeholder: nil, optional: false, symbol_position: :leading, default_text_answer: nil, hint: nil)
27
+ raise 'Missing label' if label.nil?
28
+ raise 'Missing identifier' if identifier.nil?
29
+
30
+ { item_type: :number, number_type: :number, label: label, identifier: identifier, placeholder: placeholder, optional: optional, symbol_position: symbol_position, default_text_answer: default_text_answer, hint: hint }
31
+ end
32
+
33
+ def mw_form_text(label:, identifier:, placeholder: nil, optional: false, multiline: false, default_text_answer: nil)
34
+ raise 'Missing label' if label.nil?
35
+ raise 'Missing identifier' if identifier.nil?
36
+
37
+ { item_type: :text, label: label, identifier: identifier, placeholder: placeholder, optional: optional, multiline: multiline, default_text_answer: default_text_answer }
38
+ end
39
+
40
+ def mw_form_date(label:, identifier:, optional: false, default_date_time_answer: nil)
41
+ raise 'Missing label' if label.nil?
42
+ raise 'Missing identifier' if identifier.nil?
43
+
44
+ { item_type: :date, date_type: :calendar, label: label, identifier: identifier, optional: optional, default_date_time_answer: default_date_time_answer }
45
+ end
46
+
47
+ def mw_form_time(label:, identifier:, optional: false, default_date_time_answer: nil)
48
+ raise 'Missing label' if label.nil?
49
+ raise 'Missing identifier' if identifier.nil?
50
+
51
+ { item_type: :time, label: label, identifier: identifier, optional: optional, default_date_time_answer: default_date_time_answer }
52
+ end
53
+
54
+ def mw_form_email(label:, identifier:, placeholder: nil, optional: false, default_text_answer: nil)
55
+ raise 'Missing label' if label.nil?
56
+ raise 'Missing identifier' if identifier.nil?
57
+
58
+ { item_type: :email, label: label, identifier: identifier, placeholder: placeholder, optional: optional, default_text_answer: default_text_answer }
59
+ end
60
+
61
+ def mw_form_password(label:, identifier:, placeholder: nil, optional: false, default_text_answer: nil, hint: nil)
62
+ raise 'Missing label' if label.nil?
63
+ raise 'Missing identifier' if identifier.nil?
64
+
65
+ { item_type: :secure, label: label, identifier: identifier, placeholder: placeholder, optional: optional, default_text_answer: default_text_answer, hint: hint }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module List
5
+ def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, preview_url: nil)
6
+ { id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name, imageURL: preview_url }.compact
7
+ end
8
+
9
+ def mw_list_search_suggestion(id: self.id, text:, section_name:, sf_symbol_name: nil)
10
+ {id: id.to_s, text: text, sectionName: section_name, sfSymbolName: sf_symbol_name}.compact
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Map
5
+ def mw_map_item(id: self.id, text:, detail_text: nil, latitude:, longitude:)
6
+ {id: id.to_s, text: text, detailText: detail_text, latitude: latitude, longitude: longitude}.compact
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module PieChart
5
+ def mw_pie_chart_item(id: self.id, label:, value:)
6
+ {id: id, label: label, value: value}.compact
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Question
5
+ QUESTION_STYLES = [:single_choice, :multiple_choice]
6
+
7
+ def mw_text_choice_question(question:, style:, text_choices:)
8
+ raise 'Missing question' if question.empty?
9
+ raise 'Text Choices should be a hash' unless text_choices.is_a?(Hash)
10
+
11
+ validate_question_style!(style)
12
+
13
+ text_choices_a = text_choices.map{|k, v| {_class: "ORKTextChoice", exclusive: false, text: k, value: v} }.to_a
14
+ { question: question, answerFormat: { _class: "ORKTextChoiceAnswerFormat", style: camelcase_converter(style.to_s, first_letter: :lower), textChoices: text_choices_a}}
15
+ end
16
+
17
+ private
18
+
19
+ def validate_question_style!(style)
20
+ raise 'Unknown style' unless QUESTION_STYLES.include?(style)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Stack
5
+ CONTENT_MODE_OPTIONS = [:scale_aspect_fill, :scale_aspect_fit]
6
+
7
+ def mw_display_text(text:, label: nil)
8
+ {type: :text, label: label, text: text.to_s}.compact
9
+ end
10
+
11
+ def mw_display_image(preview_url:, attachment_url:, content_mode: :scale_aspect_fill)
12
+ validate_content_mode!(content_mode)
13
+
14
+ {type: :image, contentMode: camelcase_converter(content_mode.to_s, first_letter: :lower), previewURL: preview_url, url: attachment_url}
15
+ end
16
+
17
+ def mw_display_unsplash_image(image_url)
18
+ if image_url.start_with? "https://unsplash.com/photos"
19
+ unsplash_id = image_url.split('/').last
20
+ image_url = "https://source.unsplash.com/#{unsplash_id}/800x600"
21
+ end
22
+
23
+ {type: :image, previewURL: image_url, url: image_url}.compact
24
+ end
25
+
26
+ def mw_display_video(preview_url:, attachment_url:)
27
+ {type: :video, previewURL: preview_url, url: attachment_url}
28
+ end
29
+
30
+ def mw_display_button(label:, style: :primary, on_success: :forward, sf_symbol_name: nil, material_icon_name: nil)
31
+ validate_on_success!(on_success)
32
+ validate_button_style!(style)
33
+
34
+ {type: :button, label: label, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
35
+ end
36
+
37
+ def mw_display_delete_button(url:, label: "Delete", method: :delete, style: :danger, on_success: :backward)
38
+ validate_on_success!(on_success)
39
+ validate_button_style!(style)
40
+
41
+ {type: :button, label: label, url: url, method: method, style: style, onSuccess: on_success, sfSymbolName: 'trash', materialIconName: 'delete'}.compact
42
+ end
43
+
44
+ def mw_display_url_button(url:, label:, method: :put, style: :primary, confirm_title: nil, confirm_text: nil, on_success: :reload, sf_symbol_name: nil, material_icon_name: nil)
45
+ validate_on_success!(on_success)
46
+ validate_button_style!(style)
47
+
48
+ {type: :button, label: label, url: url, method: method, style: style, confirmTitle: confirm_title, confirmText: confirm_text, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
49
+ end
50
+ alias_method :mw_display_button_for_url, :mw_display_url_button
51
+
52
+ def mw_display_system_url_button(label:, apple_system_url: nil, android_deep_link: nil, style: :primary, sf_symbol_name: nil, material_icon_name: nil)
53
+ validate_button_style!(style)
54
+ raise 'Invalid android_deep_link' if android_deep_link && !android_deep_link.start_with?('http')
55
+
56
+ {type: :button, label: label, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, style: style, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
57
+ end
58
+ alias_method :mw_display_button_for_system_url, :mw_display_system_url_button
59
+
60
+ def mw_display_modal_workflow_button(label:, modal_workflow_name:, style: :primary, on_success: :none, sf_symbol_name: nil, material_icon_name: nil)
61
+ validate_on_success!(on_success)
62
+ validate_button_style!(style)
63
+
64
+ {type: :button, label: label, modalWorkflow: modal_workflow_name, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
65
+ end
66
+ alias_method :mw_display_button_for_modal_workflow, :mw_display_modal_workflow_button
67
+
68
+ private
69
+ def validate_content_mode!(on_success)
70
+ raise 'Unknown content_mode' unless CONTENT_MODE_OPTIONS.include?(on_success)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module StyledContent
5
+ module Grid
6
+ def mw_grid_large_section(id:, text:)
7
+ raise 'Missing id' if id.nil?
8
+ raise 'Missing text' if text.nil?
9
+
10
+ { id: id, text: text, type: :largeSection }
11
+ end
12
+
13
+ def mw_grid_small_section(id:, text:)
14
+ raise 'Missing id' if id.nil?
15
+ raise 'Missing text' if text.nil?
16
+
17
+ { id: id, text: text, type: :smallSection }
18
+ end
19
+
20
+ def mw_grid_item(id: self.id, text:, detail_text: nil, preview_url: nil)
21
+ raise 'Missing id' if id.nil?
22
+ raise 'Missing text' if text.nil?
23
+
24
+ { id: id, text: text, type: :item, detailText: detail_text, imageURL: preview_url }.compact
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module StyledContent
5
+ module Stack
6
+ def mw_stack_title(id:, title:)
7
+ raise 'Missing id' if id.nil?
8
+ raise 'Missing title' if title.nil?
9
+
10
+ { id: id, title: title, type: :title }
11
+ end
12
+
13
+ def mw_stack_text(id:, text:)
14
+ raise 'Missing id' if id.nil?
15
+ raise 'Missing text' if text.nil?
16
+
17
+ { id: id, text: text, type: :text }
18
+ end
19
+
20
+ def mw_stack_list_item(id:, text:, detail_text: nil, preview_url: nil)
21
+ raise 'Missing id' if id.nil?
22
+ raise 'Missing text' if text.nil?
23
+
24
+ { id: id.to_s, text: text, detailText: detail_text, type: :listItem, imageURL: preview_url }.compact
25
+ end
26
+
27
+ def mw_stack_button(id:, label:, url: nil, method: :nil, on_success: :none, style: :primary, modal_workflow_name: nil, link_url: nil, sf_symbol_name: nil, apple_system_url: nil, android_deep_link: nil, confirm_title: nil, confirm_text: nil)
28
+ raise 'Missing id' if id.nil?
29
+ raise 'Missing label' if label.nil?
30
+
31
+ validate_on_success!(on_success)
32
+ validate_button_style!(style)
33
+
34
+ { id: id, type: :button, label: label, url: url, method: method, onSuccess: on_success, style: style, modalWorkflow: modal_workflow_name, linkURL: link_url, sfSymbolName: sf_symbol_name, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, confirmTitle: confirm_title, confirmText: confirm_text }.compact
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,163 +1,34 @@
1
1
  module MobileWorkflow
2
2
  module Displayable
3
- extend ActiveSupport::Concern
4
- include Rails.application.routes.url_helpers
5
-
6
- ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
7
- BUTTON_STYLES = [:primary, :outline, :danger]
8
- CONTENT_MODE_OPTIONS = [:scale_aspect_fill, :scale_aspect_fit]
9
- QUESTION_STYLES = [:single_choice, :multiple_choice]
10
-
11
- def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, image_attachment: nil)
12
- mw_list_item = {id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}
13
- mw_list_item[:imageURL] = preview_url(image_attachment, options: { resize_to_fill: [200, 200] }) if image_attachment
14
- mw_list_item.compact
15
- end
16
-
17
- def mw_list_search_suggestion(id: self.id, text:, section_name:, sf_symbol_name: nil)
18
- {id: id.to_s, text: text, sectionName: section_name, sfSymbolName: sf_symbol_name}.compact
19
- end
20
-
21
- def mw_map_item(id: self.id, text:, detail_text: nil, latitude:, longitude:)
22
- {id: id.to_s, text: text, detailText: detail_text, latitude: latitude, longitude: longitude}.compact
23
- end
24
-
25
- def mw_pie_chart_item(id: self.id, label:, value:)
26
- {id: id, label: label, value: value}.compact
27
- end
28
-
29
- def mw_display_text(text:, label: nil)
30
- {type: :text, label: label, text: text.to_s}.compact
31
- end
32
-
33
- def mw_display_image(attachment, content_mode: :scale_aspect_fill, options: { resize_to_fill: [1200, 600] })
34
- validate_content_mode!(content_mode)
35
-
36
- {type: :image, contentMode: content_mode.to_s.camelize(:lower), previewURL: preview_url(attachment, options: options), url: attachment_url(attachment)}
37
- end
38
-
39
- def mw_display_unsplash_image(image_url)
40
- if image_url.start_with? "https://unsplash.com/photos"
41
- unsplash_id = image_url.split('/').last
42
- image_url = "https://source.unsplash.com/#{unsplash_id}/800x600"
43
- end
44
-
45
- {type: :image, previewURL: image_url, url: image_url}.compact
46
- end
47
-
48
- def mw_display_video(attachment, preview_options: { resize_to_fill: [600, 1200] })
49
- {type: :video, previewURL: preview_url(attachment, options: preview_options), url: attachment_url(attachment)}
50
- end
51
-
52
- def mw_display_button(label:, style: :primary, on_success: :forward, sf_symbol_name: nil, material_icon_name: nil)
53
- validate_on_success!(on_success)
54
- validate_button_style!(style)
55
-
56
- {type: :button, label: label, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
57
- end
58
-
59
- def mw_display_delete_button(url:, label: "Delete", method: :delete, style: :danger, on_success: :backward)
60
- validate_on_success!(on_success)
61
- validate_button_style!(style)
62
-
63
- {type: :button, label: label, url: url, method: method, style: style, onSuccess: on_success, sfSymbolName: 'trash', materialIconName: 'delete'}.compact
64
- end
65
-
66
- def mw_display_url_button(label:, url:, method: :put, style: :primary, confirm_title: nil, confirm_text: nil, on_success: :reload, sf_symbol_name: nil, material_icon_name: nil)
67
- validate_on_success!(on_success)
68
- validate_button_style!(style)
69
-
70
- {type: :button, label: label, url: url, method: method, style: style, confirmTitle: confirm_title, confirmText: confirm_text, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
71
- end
72
- alias_method :mw_display_button_for_url, :mw_display_url_button
73
-
74
- def mw_display_system_url_button(label:, apple_system_url: nil, android_deep_link: nil, style: :primary, sf_symbol_name: nil, material_icon_name: nil)
75
- validate_button_style!(style)
76
- raise 'Invalid android_deep_link' if android_deep_link && !android_deep_link.start_with?('http')
77
-
78
- {type: :button, label: label, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, style: style, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
79
- end
80
- alias_method :mw_display_button_for_system_url, :mw_display_system_url_button
81
-
82
- def mw_display_modal_workflow_button(label:, modal_workflow_name:, style: :primary, on_success: :none, sf_symbol_name: nil, material_icon_name: nil)
83
- validate_on_success!(on_success)
84
- validate_button_style!(style)
85
-
86
- {type: :button, label: label, modalWorkflow: modal_workflow_name, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
87
- end
88
- alias_method :mw_display_button_for_modal_workflow, :mw_display_modal_workflow_button
89
-
90
- def mw_text_choice_question(question:, style:, text_choices:)
91
- raise 'Missing question' if question.blank?
92
- raise 'Text Choices should be a hash' unless text_choices.is_a?(Hash)
93
- validate_question_style!(style)
94
-
95
- text_choices_a = text_choices.map{|k, v| {_class: "ORKTextChoice", exclusive: false, text: k, value: v} }.to_a
96
- { question: question, answerFormat: { _class: "ORKTextChoiceAnswerFormat", style: style.to_s.camelize(:lower), textChoices: text_choices_a}}
97
- end
98
-
99
- def mw_grid_large_section(id:, text:)
100
- raise 'Missing id' if id.nil?
101
- raise 'Missing text' if text.nil?
102
-
103
- { id: id, text: text, type: :largeSection }
104
- end
105
-
106
- def mw_grid_small_section(id:, text:)
107
- raise 'Missing id' if id.nil?
108
- raise 'Missing text' if text.nil?
109
-
110
- { id: id, text: text, type: :smallSection }
3
+ def self.included(base)
4
+ base.extend(Steps::Form)
111
5
  end
6
+
7
+ include Steps::List
8
+ include Steps::Map
9
+ include Steps::PieChart
10
+ include Steps::Question
11
+ include Steps::Stack
12
+ include Steps::StyledContent::Grid
13
+ include Steps::StyledContent::Stack
14
+
15
+ BUTTON_STYLES = [:primary, :outline, :danger, :textOnly]
16
+ ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
112
17
 
113
- def mw_grid_item(id: self.id, text:, image_attachment: nil, options: { resize_to_fill: [1560, 877.5] })
114
- raise 'Missing id' if id.nil?
115
- raise 'Missing text' if text.nil?
116
-
117
- item = { id: id, text: text, type: :item }
118
- item[:imageURL] = preview_url(image_attachment, options: options) if image_attachment
119
- item
120
- end
121
-
122
18
  private
123
19
  def validate_on_success!(on_success)
124
20
  raise 'Unknown on_success action' unless ON_SUCCESS_OPTIONS.include?(on_success)
125
21
  end
126
22
 
127
- def validate_content_mode!(on_success)
128
- raise 'Unknown content_mode' unless CONTENT_MODE_OPTIONS.include?(on_success)
129
- end
130
-
131
23
  def validate_button_style!(style)
132
24
  raise 'Unknown style' unless BUTTON_STYLES.include?(style)
133
25
  end
134
-
135
- def validate_question_style!(style)
136
- raise 'Unknown style' unless QUESTION_STYLES.include?(style)
137
- end
138
-
139
- def preview_url(attachment, options:)
140
- return nil unless attachment.attached?
141
-
142
- if attachment.image?
143
- Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
144
- elsif attachment.previewable?
145
- Rails.application.routes.url_helpers.rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
146
- else
147
- return nil
148
- end
149
- end
150
26
 
151
- def attachment_url(attachment)
152
- return nil unless attachment.attached?
153
-
154
- Rails.application.routes.url_helpers.rails_blob_url(attachment, host: heroku_attachment_host)
155
- end
27
+ def camelcase_converter(string, first_letter: :upper)
28
+ string = string.split("_").map(&:capitalize).join
29
+ return string unless first_letter == :lower
156
30
 
157
- def heroku_attachment_host
158
- # TODO: MBS - move this to a configuration property
159
- app_name = Rails.env.test? ? 'test-app' : ENV.fetch('HEROKU_APP_NAME')
160
- "https://#{app_name}.herokuapp.com"
31
+ string[0].downcase + string[1..-1]
161
32
  end
162
33
  end
163
34
  end
@@ -67,6 +67,18 @@ module MobileWorkflow
67
67
  end
68
68
  end
69
69
 
70
+ def generate_deserializers
71
+ say "Generating deserializers"
72
+
73
+ model_name_to_properties.each_pair do |model_name, properties|
74
+ @deserializer_class = model_name
75
+ @deserializer_properties = properties.split(' ').map { |attribute| attribute.split(':').first }
76
+
77
+ template("deserializer.rb.erb", "app/services/#{model_name}_deserializer.rb")
78
+ template("deserializer_spec.rb.erb", "spec/services/#{model_name}_deserializer_spec.rb")
79
+ end
80
+ end
81
+
70
82
  def generate_controllers_and_routes
71
83
  say "Generating controllers"
72
84
  controller_name_to_actions = open_api_spec.controller_name_to_actions
@@ -1,4 +1,5 @@
1
1
  class ApplicationRecord < ActiveRecord::Base
2
+ include MobileWorkflow::Attachable
2
3
  include MobileWorkflow::Displayable
3
4
 
4
5
  self.abstract_class = true
@@ -0,0 +1,13 @@
1
+ class <%= @deserializer_class.capitalize %>Deserializer
2
+ def self.parse(params)
3
+ <% @deserializer_properties.each do |attribute| -%>
4
+ <%= attribute %> = params.dig(:payload, :<%= attribute %>)
5
+ <% end -%>
6
+
7
+ { <%= @deserializer_class %>: {
8
+ <% @deserializer_properties.each do |attribute| -%>
9
+ <%= attribute %>: <%= attribute %>,
10
+ <% end -%>
11
+ }.compact }
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+ require 'json'
5
+
6
+ RSpec.describe <%= @deserializer_class.capitalize %>Deserializer do
7
+ let(:params) { {} }
8
+
9
+ describe 'ok' do
10
+ let(:params) { { payload: payload } }
11
+ let(:parsed_params) { { <%= @deserializer_class %>: <%= @deserializer_class %>_params } }
12
+ let(:payload) {
13
+ {
14
+ <% @deserializer_properties.each do |attribute| -%>
15
+ <%= attribute %>: 'string',
16
+ <% end -%>
17
+ }
18
+ }
19
+ let(:<%= @deserializer_class %>_params) {
20
+ {
21
+ <% @deserializer_properties.each do |attribute| -%>
22
+ <%= attribute %>: 'string',
23
+ <% end -%>
24
+ }
25
+ }
26
+
27
+ it { expect(described_class.parse(params)).to eq parsed_params }
28
+ end
29
+ end
@@ -1,4 +1,8 @@
1
1
  class User < ApplicationRecord
2
2
  has_secure_password
3
+
4
+ has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, dependent: :destroy
5
+ has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, dependent: :destroy
6
+
3
7
  validates :email, presence: true, uniqueness: true
4
8
  end
@@ -5,11 +5,18 @@ module MobileWorkflow
5
5
 
6
6
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
7
7
  source_root File.join(File.dirname(ActiveRecord::Generators::ModelGenerator.instance_method(:create_migration_file).source_location.first), "templates")
8
-
8
+
9
+ class_option :doorkeeper_oauth, type: :boolean, default: false
10
+
9
11
  def create_model_file
10
12
  template File.join(File.dirname(__FILE__), "templates", "model.rb.erb"), File.join('app/models', class_path, "#{file_name}.rb")
11
13
  end
12
-
14
+
15
+ private
16
+
17
+ def doorkeeper_oauth?
18
+ options[:doorkeeper_oauth]
19
+ end
13
20
  end
14
21
  end
15
22
  end
@@ -41,11 +41,13 @@ class <%= controller_class_name %>Controller < ApiController
41
41
  # passport_id = params.dig(:payload, :choose_passport, :selected, :id)
42
42
 
43
43
  Rails.logger.debug "Pre-rewrite params: #{params}"
44
- # Do your rewriting here
44
+
45
+ parsed_params = <%= controller_class_name.singularize %>Deserializer.parse(params)
46
+ params.merge!(parsed_params)
45
47
  end
46
48
 
47
49
  def <%= singular_table_name.underscore %>_params
48
- params.require(:payload).permit(<%= permitted_params %>)
50
+ params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
49
51
  end
50
52
  <% end -%>
51
53
  end
@@ -8,18 +8,33 @@ class <%= class_name %> < <%= parent_class_name.classify %>
8
8
  <% end -%>
9
9
  <% attributes.select(&:attachments?).each do |attribute| -%>
10
10
  has_many_attached :<%= attribute.name %>
11
+ <% end -%>
12
+ <% if class_name == 'User' -%>
13
+ <% if doorkeeper_oauth? -%>
14
+ has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, dependent: :destroy
15
+ has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, dependent: :destroy
16
+ <% end -%>
17
+ <% attributes.each do |attribute| -%>
18
+ <% if attribute.name == 'email' -%>
19
+ validates :email, presence: true, uniqueness: { case_sensitive: false }
20
+ before_validation :downcase_email, if: :email_changed?
21
+
22
+ def downcase_email
23
+ self.email = email.downcase
24
+ end
25
+ <% end -%>
26
+ <% end -%>
11
27
  <% end -%>
12
28
 
13
29
  def list_item_as_json
14
30
  mw_list_item(text: <%= attributes.first.name %>)
15
31
  end
16
-
32
+
17
33
  def display_as_json
18
34
  [
19
35
  mw_display_text(label: 'ID', text: id.to_s),
20
36
  mw_display_text(label: 'Text', text: <%= attributes.first.name %>)
21
37
  ]
22
38
  end
23
-
24
39
  end
25
40
  <% end -%>
@@ -0,0 +1,9 @@
1
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb'
2
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb'
3
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/form.rb'
4
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/list.rb'
5
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/map.rb'
6
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb'
7
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/question.rb'
8
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/stack.rb'
9
+ require_relative '../../app/models/concerns/mobile_workflow/displayable.rb'
@@ -1,9 +1,11 @@
1
1
  module MobileWorkflow
2
- class Engine < ::Rails::Engine
3
- isolate_namespace MobileWorkflow
4
-
5
- config.generators do |g|
6
- g.test_framework :rspec
2
+ if defined?(Rails)
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace MobileWorkflow
5
+
6
+ config.generators do |g|
7
+ g.test_framework :rspec
8
+ end
7
9
  end
8
10
  end
9
11
  end
@@ -1,5 +1,5 @@
1
1
  module MobileWorkflow
2
- VERSION = '0.7.6'
2
+ VERSION = '0.7.7'
3
3
  RUBY_VERSION = '2.7.3'
4
4
  RAILS_VERSION = '6.1.3.1'
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require "mobile_workflow/engine"
2
+ require "mobile_workflow/displayable"
2
3
 
3
4
  module MobileWorkflow
4
5
  require 'mobile_workflow/railtie' if defined?(Rails)
metadata CHANGED
@@ -1,15 +1,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobile_workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brooke-Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-02 00:00:00.000000000 Z
11
+ date: 2021-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-s3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.60'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.60.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.60'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.60.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: aws-sdk-sns
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.23'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.23'
13
47
  - !ruby/object:Gem::Dependency
14
48
  name: rails
15
49
  requirement: !ruby/object:Gem::Requirement
@@ -97,9 +131,19 @@ files:
97
131
  - app/controllers/concerns/mobile_workflow/s3_storable.rb
98
132
  - app/controllers/mobile_workflow/sns_notifications_controller.rb
99
133
  - app/helpers/mobile_workflow/application_helper.rb
134
+ - app/jobs/mobile_workflow/add_attachment_job.rb
100
135
  - app/jobs/mobile_workflow/application_job.rb
101
136
  - app/mailers/mobile_workflow/application_mailer.rb
137
+ - app/models/concerns/mobile_workflow/attachable.rb
102
138
  - app/models/concerns/mobile_workflow/displayable.rb
139
+ - app/models/concerns/mobile_workflow/displayable/steps/form.rb
140
+ - app/models/concerns/mobile_workflow/displayable/steps/list.rb
141
+ - app/models/concerns/mobile_workflow/displayable/steps/map.rb
142
+ - app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb
143
+ - app/models/concerns/mobile_workflow/displayable/steps/question.rb
144
+ - app/models/concerns/mobile_workflow/displayable/steps/stack.rb
145
+ - app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb
146
+ - app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb
103
147
  - app/views/layouts/mobile_workflow/application.html.erb
104
148
  - bin/mwf
105
149
  - config/routes.rb
@@ -116,6 +160,8 @@ files:
116
160
  - lib/generators/mobile_workflow/install/templates/app/views/layouts/application.html.erb
117
161
  - lib/generators/mobile_workflow/install/templates/app/views/sessions/new.html.erb
118
162
  - lib/generators/mobile_workflow/install/templates/create_users.rb
163
+ - lib/generators/mobile_workflow/install/templates/deserializer.rb.erb
164
+ - lib/generators/mobile_workflow/install/templates/deserializer_spec.rb.erb
119
165
  - lib/generators/mobile_workflow/install/templates/seeds.rb.erb
120
166
  - lib/generators/mobile_workflow/install/templates/sessions_controller.rb.erb
121
167
  - lib/generators/mobile_workflow/install/templates/spec/factories/users.rb
@@ -133,6 +179,7 @@ files:
133
179
  - lib/mobile_workflow/cli/aws_backend.rb
134
180
  - lib/mobile_workflow/cli/dokku_backend.rb
135
181
  - lib/mobile_workflow/cli/heroku_backend.rb
182
+ - lib/mobile_workflow/displayable.rb
136
183
  - lib/mobile_workflow/engine.rb
137
184
  - lib/mobile_workflow/open_api_spec/parser.rb
138
185
  - lib/mobile_workflow/railtie.rb