effective_developer 0.6.3 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e76b8818691ec2238e08a818d209ba7e4d1746734ef3c7c2c2cbe8f4e08aa12
4
- data.tar.gz: a283fca85ffd1c0dc6d73287bd6c239c898e6b6e2aa8500ebc08642960f7ad5f
3
+ metadata.gz: 5ca9f6ec3b73f4a7f2e31b382f9030769e13b82b43835bd7a74f4efa41a07c24
4
+ data.tar.gz: f21c929b19f4d9c4e645f90cc85cda28f4d0e3c1413689b294b7326bde095359
5
5
  SHA512:
6
- metadata.gz: 0dc45f6f25c607353f5ea9651cda2054e2d6f2996fac603ebd37df05191d4a2d7981662e7e68681163856a566eeb452c4b0030623d130f2d33507bea36956548
7
- data.tar.gz: 8a4ec7eab0237e8f0ac37a80e1cb7ea3974514b8228a6f8711a9fa00b66e0d2cecb5b22d336c28afd954bb916337b809a1205b8820d3637fb66574829710b73d
6
+ metadata.gz: 8ce3d22243ef79b452009c2e20e993bea861489b4cca95f4eba7d27f0fa11a305e272298d19cbd8fab2b829d1004b690202c6769dfab239f671e82ddcc49a524
7
+ data.tar.gz: 54fd14f769c558937de317edb4e00dc3114775a1f58b948fe5723bea590cecd1a0c9d7958aefac45b06a82558582b527f3bf0dd0224f6dc6d61fd3eabe6cfb61
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2018 Code and Effect Inc.
1
+ Copyright 2021 Code and Effect Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,17 @@
1
+ module Effective
2
+ class AssetReplacerJob < ::ApplicationJob
3
+
4
+ queue_as :default
5
+
6
+ if defined?(Sidekiq)
7
+ # The retry setting works. But none of these waits seem to. Still getting exponential backoff.
8
+ sidekiq_options retry: 2, retry_in: 10, wait: 10
9
+ sidekiq_retry_in { |count, exception| 10 }
10
+ end
11
+
12
+ def perform(attachment, box)
13
+ Effective::AssetReplacer.new.replace_attachment!(attachment, box)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Effective
2
+ class SnippetReplacerJob < ::ApplicationJob
3
+
4
+ queue_as :default
5
+
6
+ if defined?(Sidekiq)
7
+ # The retry setting works. But none of these waits seem to. Still getting exponential backoff.
8
+ sidekiq_options retry: 2, retry_in: 10, wait: 10
9
+ sidekiq_retry_in { |count, exception| 10 }
10
+ end
11
+
12
+ def perform(region)
13
+ Effective::SnippetReplacer.new.replace_region!(region)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,156 @@
1
+ # Looks at the Effective::Asset and Effective::Attachments and converts to ActiveStorage
2
+ #
3
+ # 1. Put in the has_attached_files to each model to be upgraded
4
+ # 2. Remove the acts_as_asset_box
5
+
6
+ require 'timeout'
7
+
8
+ module Effective
9
+ class AssetReplacer
10
+ include ActiveStorage::Blob::Analyzable
11
+
12
+ BATCH_SIZE = 500
13
+ TIMEOUT = 120
14
+
15
+ def replace!(skip_existing: false)
16
+ verify!
17
+
18
+ attachments = attachments_to_process().to_a
19
+
20
+ while(true)
21
+ wait_for_active_job!
22
+
23
+ puts "\nEnqueuing #{attachments.length} attachments with ids #{attachments.first.id} to #{attachments.last.id}"
24
+
25
+ attachments.each do |attachment|
26
+ asset = attachment.asset
27
+ attachable = attachment.attachable
28
+ next if asset.blank? || attachable.blank?
29
+
30
+ box = attachment.box.singularize
31
+ boxes = attachment.box
32
+
33
+ one = attachable.respond_to?(box) && attachable.send(box).kind_of?(ActiveStorage::Attached::One)
34
+ many = attachable.respond_to?(boxes) && attachable.send(boxes).kind_of?(ActiveStorage::Attached::Many)
35
+ box = (one ? box : boxes)
36
+
37
+ if skip_existing
38
+ existing = Array(attachable.send(box))
39
+
40
+ if existing.any? { |obj| obj.respond_to?(:filename) && obj.filename.to_s == asset.file_name }
41
+ puts("Skipping existing #{attachable.class.name} #{attachable.id} #{box} #{asset.file_name}.")
42
+ next
43
+ end
44
+ end
45
+
46
+ Effective::AssetReplacerJob.perform_later(attachment, box)
47
+ end
48
+
49
+ attachments = attachments_to_process().where.not(id: attachments.map(&:id))
50
+ break if attachments.to_a.blank?
51
+
52
+ GC.start
53
+ end
54
+
55
+ puts "\nAll Done. Have a great day."
56
+ true
57
+ end
58
+
59
+ # This is called on the background by the AssetReplacerJob
60
+ def replace_attachment!(attachment, box)
61
+ raise('expected an Effective::Attachment') unless attachment.kind_of?(Effective::Attachment)
62
+ puts("Processing attachment ##{attachment.id}")
63
+
64
+ asset = attachment.asset
65
+ attachable = attachment.attachable
66
+
67
+ attachable.replacing_asset = true if attachable.respond_to?(:replacing_asset=)
68
+
69
+ Timeout.timeout(TIMEOUT) do
70
+ attachable.send(box).attach(
71
+ io: URI.open(asset.url),
72
+ filename: asset.file_name,
73
+ content_type: asset.content_type.presence,
74
+ identify: (asset.content_type.blank?)
75
+ )
76
+
77
+ attachment.update_column(:replaced, true)
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ def verify!
84
+ raise('expected effective assets') unless defined?(Effective::Asset)
85
+ raise('expected active storage') unless defined?(ActiveStorage)
86
+
87
+ unless Effective::Attachment.new.respond_to?(:replaced?)
88
+ raise('please add a replaced boolean to Effective::Attachment. add_column :attachments, :replaced, :boolean')
89
+ end
90
+
91
+ (Effective::Attachment.all.pluck(:attachable_type, :box).uniq).each do |name, boxes|
92
+ next if name.blank? || boxes.blank?
93
+
94
+ box = boxes.singularize
95
+
96
+ klass = name.safe_constantize
97
+ raise("invalid class #{klass}") unless klass.present?
98
+
99
+ instance = klass.new
100
+
101
+ if instance.respond_to?(:effective_assets)
102
+ raise("please remove acts_as_asset_box() from #{klass.name}")
103
+ end
104
+
105
+ unless instance.respond_to?(box) || instance.respond_to?(boxes)
106
+ raise("expected #{klass.name} to has_one_attached :#{box} or has_many_attached :#{boxes}")
107
+ end
108
+
109
+ one = instance.respond_to?(box) && instance.send(box).kind_of?(ActiveStorage::Attached::One)
110
+ many = instance.respond_to?(boxes) && instance.send(boxes).kind_of?(ActiveStorage::Attached::Many)
111
+
112
+ unless one.present? || many.present?
113
+ raise("expected #{klass.name} to has_one_attached :#{box} or has_many_attached :#{boxes}")
114
+ end
115
+ end
116
+
117
+ puts 'All attachment classes verified.'
118
+
119
+ true
120
+ end
121
+
122
+ def reset!
123
+ Effective::Attachment.update_all(replaced: false)
124
+ end
125
+
126
+ private
127
+
128
+ def wait_for_active_job!
129
+ while(true)
130
+ if(jobs = enqueued_jobs_count) > (BATCH_SIZE / 10)
131
+ print '.'; sleep(3)
132
+ else
133
+ break
134
+ end
135
+ end
136
+ end
137
+
138
+ # The last BATCH_SIZE attachments
139
+ def attachments_to_process
140
+ Effective::Attachment.all
141
+ .includes(:asset)
142
+ .where(replaced: [nil, false])
143
+ .reorder(id: :desc)
144
+ .limit(BATCH_SIZE)
145
+ end
146
+
147
+ def enqueued_jobs_count
148
+ if Rails.application.config.active_job.queue_adapter == :sidekiq
149
+ Sidekiq::Stats.new.enqueued.to_i
150
+ else
151
+ ActiveJob::Base.queue_adapter.enqueued_jobs.count
152
+ end
153
+ end
154
+
155
+ end
156
+ end
@@ -0,0 +1,197 @@
1
+ # Upgrades simple_form_for to effective_form_with
2
+ module Effective
3
+ class FormUpgrader
4
+
5
+ def initialize(folder: 'app/views/')
6
+ @folders = Array(folder)
7
+ end
8
+
9
+ def upgrade!
10
+ @folders.each do |folder|
11
+ Dir.glob(folder + '**/*').each do |path|
12
+ next if File.directory?(path)
13
+ next unless path.include?('.html')
14
+
15
+ writer = Effective::CodeWriter.new(path)
16
+
17
+ name = path.split('/')[0...-1] - ['app', 'views']
18
+ resource = Effective::Resource.new(name)
19
+
20
+ if writer.find { |line| line.include?('simple_form_for') }
21
+ upgrade_simple_form(writer, resource)
22
+ elsif writer.find { |line| line.include?('semantic_form_for') }
23
+ upgrade_formtastic(writer, resource)
24
+ elsif writer.find { |line| line.include?('form_for') }
25
+ upgrade_form_for(writer, resource)
26
+ else
27
+ next # Nothing to do
28
+ end
29
+
30
+ writer.write!
31
+ end
32
+ end
33
+
34
+ puts 'All Done. Have a great day.'
35
+ true
36
+ end
37
+
38
+ private
39
+
40
+ SIMPLE_FORM_FOR_REGEX = /simple_form_for( |\()(\[:[^,]+, ?[^,]+\])?(([^,]+),)?.+do \|(\w+)\|/
41
+ SIMPLE_FORM_INPUT_ATTRIBUTE = /\.input( |\():(\w+)/
42
+ SIMPLE_FORM_INPUT_AS_ONE = /(as: :(\w+))/
43
+ SIMPLE_FORM_INPUT_AS_TWO = /(:as => :(\w+))/
44
+ SIMPLE_FORM_INPUT_COLLECTION_ONE = /(collection: ([^,]+?))(,|$)/
45
+ SIMPLE_FORM_INPUT_COLLECTION_TWO = /(:collection => :([^,]+?))(,|$)/
46
+
47
+ def upgrade_simple_form(writer, resource)
48
+ puts "Upgrading simple form: #{writer.filename}"
49
+
50
+ letter = nil
51
+ model = nil
52
+
53
+ # Replace simple_form_for
54
+ writer.all { |line| line.include?('simple_form_for') }.each do |line|
55
+ content = writer.lines[line]
56
+ matched = content.match(SIMPLE_FORM_FOR_REGEX)
57
+ raise("unable to match simple_form_for from:\n#{content}") unless matched.present?
58
+
59
+ original = matched[0]
60
+ model = matched[2] || matched[4]
61
+ letter = matched[5]
62
+
63
+ raise("unable to determine simple_form_for subject from:\n#{content}") unless original && model && letter
64
+
65
+ content.sub!(original, "effective_form_with(model: #{model}) do |#{letter}|")
66
+ writer.replace(line, content)
67
+ end
68
+
69
+ # Try to figure out klass again if its missing from filename
70
+ if resource.klass.blank? && model.present?
71
+ name = model.sub('[', '').sub(']', '').sub(' ', '').split(',').map do |value|
72
+ value.sub('current_', '').sub('@', '').sub(':', '')
73
+ end
74
+
75
+ resource = Effective::Resource.new(name)
76
+ end
77
+
78
+ if resource.klass.blank? && writer.filename.include?('/devise/')
79
+ resource = Effective::Resource.new('user')
80
+ end
81
+
82
+ if resource.klass.blank?
83
+ puts " => Warning: Unable to determine klass of #{model}"
84
+ end
85
+
86
+ # Replace .input
87
+ writer.all { |line| line.include?('.input :') }.each do |line|
88
+ content = writer.lines[line]
89
+ attribute = content.match(SIMPLE_FORM_INPUT_ATTRIBUTE)
90
+ raise("unable to match simple_form_for input attribute from\n#{content}") unless attribute.present?
91
+
92
+ as = content.match(SIMPLE_FORM_INPUT_AS_ONE) || content.match(SIMPLE_FORM_INPUT_AS_TWO)
93
+ collection = content.match(SIMPLE_FORM_INPUT_COLLECTION_ONE) || content.match(SIMPLE_FORM_INPUT_COLLECTION_TWO)
94
+
95
+ if as.present?
96
+ content.sub!(",#{as[0]}", '')
97
+ content.sub!(", #{as[0]}", '')
98
+ end
99
+
100
+ if collection.present?
101
+ content.sub!(",#{collection[0]}", ',')
102
+ content.sub!(", #{collection[0]}", ',')
103
+ content.sub!(attribute[0], "#{attribute[0]} #{collection[2]},")
104
+ end
105
+
106
+ input_type = find_input_type(resource: resource, attribute: attribute[2], as: (as[2] if as))
107
+
108
+ content.sub!('input', input_type)
109
+ writer.replace(line, content)
110
+ end
111
+
112
+ # Replace simple_fields_for
113
+ writer.all { |line| line.include?(".simple_fields_for") }.each do |line|
114
+ content = writer.lines[line]
115
+
116
+ content.sub!(".simple_fields_for", ".has_many")
117
+ writer.replace(line, content)
118
+ end
119
+
120
+ # Replace f.submit
121
+ writer.all { |line| line.include?("#{letter}.submit") }.each do |line|
122
+ content = writer.lines[line]
123
+
124
+ content.sub!("#{letter}.submit", "#{letter}.save")
125
+ writer.replace(line, content)
126
+ end
127
+
128
+ # Replace f.button :submit
129
+ writer.all { |line| line.include?(".button :submit,") }.each do |line|
130
+ content = writer.lines[line]
131
+
132
+ content.sub!(".button :submit,", ".submit")
133
+ writer.replace(line, content)
134
+ end
135
+
136
+ # Replace .form-actions
137
+ writer.all { |line| line == '.form-actions' }.each do |line|
138
+ content = writer.lines[line]
139
+
140
+ content.sub!('.form-actions', "= #{letter}.submit do")
141
+ writer.replace(line, content)
142
+ end
143
+ end
144
+
145
+ def upgrade_formtastic(writer, resource)
146
+ puts "Detected formtastic: #{writer.filename}"
147
+ end
148
+
149
+ def upgrade_form_for(writer, resource)
150
+ puts "Detected rails form_for: #{writer.filename}"
151
+ end
152
+
153
+ def find_input_type(attribute:, resource:, as: nil)
154
+ input_type = (as || resource.sql_type(attribute)).to_s
155
+
156
+ case input_type
157
+ when 'asset_box_simple_form' then 'file_field'
158
+ when 'belongs_to', 'belongs_to_polymorphic' then 'select'
159
+ when 'boolean' then 'check_box'
160
+ when 'check_boxes' then 'checks'
161
+ when 'date' then 'date_field'
162
+ when 'datetime' then 'datetime_field'
163
+ when 'decimal' then 'float_field'
164
+ when 'effective_ckeditor_text_area' then 'rich_text_area' # I guess
165
+ when 'effective_date_picker' then 'date_field'
166
+ when 'effective_date_time_picker' then 'datetime_field'
167
+ when 'effective_email' then 'email_field'
168
+ when 'effective_price' then 'price_field'
169
+ when 'effective_radio_buttons' then 'radios'
170
+ when 'effective_select' then 'select'
171
+ when 'effective_static_control' then 'static_field'
172
+ when 'effective_tel' then 'phone_field'
173
+ when 'effective_time_picker' then 'time_field'
174
+ when 'effective_url' then 'url_field'
175
+ when 'email' then 'email_field'
176
+ when 'file' then 'file_field'
177
+ when 'hidden' then 'hidden_field'
178
+ when 'integer' then 'number_field'
179
+ when 'number' then 'number_field'
180
+ when 'password' then 'password_field'
181
+ when 'phone' then 'phone_field'
182
+ when 'price' then 'price_field'
183
+ when 'radio_buttons' then 'radios'
184
+ when 'search' then 'search_field'
185
+ when 'select' then 'select'
186
+ when 'static_control' then 'static_field'
187
+ when 'string' then 'text_field'
188
+ when 'tel', 'telephone' then 'phone_field'
189
+ when 'text' then 'text_area'
190
+ when 'url' then 'url_field'
191
+ else
192
+ raise("unknown input type #{input_type} (for attribute :#{attribute})")
193
+ end
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,67 @@
1
+ # Replaces the [snippet_x] in all effective regions with static content
2
+
3
+ module Effective
4
+ class SnippetReplacer
5
+ include ActiveStorage::Blob::Analyzable
6
+ include ActionView::Helpers::UrlHelper
7
+ include ActionView::Helpers::AssetTagHelper
8
+
9
+ def replace!
10
+ raise('expected effective regions') unless defined?(Effective::Region)
11
+ raise('expected effective assets') unless defined?(Effective::Asset)
12
+ raise('expected active storage') unless defined?(ActiveStorage)
13
+
14
+ Effective::Region.with_snippets.find_each do |region|
15
+ Effective::SnippetReplacerJob.perform_later(region)
16
+ end
17
+
18
+ puts 'All Done. Background jobs are running. Have a great day.'
19
+ true
20
+ end
21
+
22
+ def replace_region!(region)
23
+ region.snippet_objects.each do |snippet|
24
+ print('.')
25
+
26
+ begin
27
+ case snippet.class.name
28
+ when 'Effective::Snippets::EffectiveAsset'
29
+ replace_effective_asset(region, snippet)
30
+ else
31
+ raise("unsupported snippet: #{snippet.class.name}")
32
+ end
33
+ rescue => e
34
+ puts "\nError: #{e}\n"
35
+ remove_snippet(region, snippet)
36
+ end
37
+ end
38
+
39
+ region.save!
40
+ end
41
+
42
+ def replace_effective_asset(region, snippet)
43
+ asset = snippet.asset
44
+ raise("Effective:Asset id=#{snippet.asset_id || 'none'} does not exist") unless asset.present?
45
+
46
+ blob = ActiveStorage::Blob.create_and_upload!(io: URI.open(asset.url), filename: asset.file_name)
47
+ url = Rails.application.routes.url_helpers.rails_blob_url(blob, only_path: true)
48
+
49
+ content = if asset.image?
50
+ image_tag(url, class: snippet.html_class, alt: snippet.link_title)
51
+ else
52
+ link_to(snippet.link_title, url, class: snippet.html_class, title: snippet.link_title)
53
+ end
54
+
55
+ region.content.sub!("[#{snippet.id}]", content.to_s)
56
+ region.snippets.delete(snippet.id)
57
+
58
+ true
59
+ end
60
+
61
+ def remove_snippet(region, snippet)
62
+ region.content.sub!("[#{snippet.id}]", '')
63
+ region.snippets.delete(snippet.id)
64
+ end
65
+
66
+ end
67
+ end
@@ -2,6 +2,9 @@ module EffectiveDeveloper
2
2
  class Engine < ::Rails::Engine
3
3
  engine_name 'effective_developer'
4
4
 
5
+ config.autoload_paths += Dir["#{config.root}/models/jobs/"]
6
+ config.eager_load_paths += Dir["#{config.root}/models/jobs/"]
7
+
5
8
  # Set up our default configuration options.
6
9
  initializer 'effective_developer.defaults', before: :load_config_initializers do |app|
7
10
  # Set up our defaults, as per our initializer template
@@ -1,3 +1,3 @@
1
1
  module EffectiveDeveloper
2
- VERSION = '0.6.3'.freeze
2
+ VERSION = '0.6.7'.freeze
3
3
  end
@@ -0,0 +1,5 @@
1
+ # bundle exec rake replace_effective_assets
2
+ desc 'Replaces effective_assets with ActiveStorage'
3
+ task :replace_effective_assets => :environment do
4
+ Effective::AssetReplacer.new.replace!
5
+ end
@@ -0,0 +1,5 @@
1
+ # bundle exec rake replace_effective_snippets
2
+ desc 'Replaces effective_assets snippets with ActiveStorage uploads'
3
+ task :replace_effective_snippets => :environment do
4
+ Effective::SnippetReplacer.new.replace!
5
+ end
@@ -0,0 +1,7 @@
1
+ # bundle exec rake upgrade_forms
2
+ # bundle exec rake upgrade_forms[app/views/admin/]
3
+ desc 'Upgrades simple_form_for to effective_form_with'
4
+ task :upgrade_forms, [:folder] => :environment do |t, args|
5
+ args.with_defaults(folder: 'app/views/')
6
+ Effective::FormUpgrader.new(folder: args.folder).upgrade!
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_developer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-31 00:00:00.000000000 Z
11
+ date: 2021-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -48,11 +48,16 @@ files:
48
48
  - MIT-LICENSE
49
49
  - README.md
50
50
  - Rakefile
51
+ - app/jobs/effective/asset_replacer_job.rb
52
+ - app/jobs/effective/snippet_replacer_job.rb
51
53
  - app/models/effective/annotator.rb
54
+ - app/models/effective/asset_replacer.rb
52
55
  - app/models/effective/code_writer.rb
53
56
  - app/models/effective/csv_importer.rb
57
+ - app/models/effective/form_upgrader.rb
54
58
  - app/models/effective/live_generator.rb
55
59
  - app/models/effective/profiler.rb
60
+ - app/models/effective/snippet_replacer.rb
56
61
  - config/effective_developer.rb
57
62
  - lib/effective_developer.rb
58
63
  - lib/effective_developer/engine.rb
@@ -100,7 +105,10 @@ files:
100
105
  - lib/tasks/effective_csv_importer.rake
101
106
  - lib/tasks/pg_pull.rake
102
107
  - lib/tasks/rename_class.rake
108
+ - lib/tasks/replace_effective_assets.rake
109
+ - lib/tasks/replace_effective_snippets.rake
103
110
  - lib/tasks/reset_pk_sequence.rake
111
+ - lib/tasks/upgrade_forms.rake
104
112
  - lib/tasks/validate.rake
105
113
  homepage: https://github.com/code-and-effect/effective_developer
106
114
  licenses: