b4um_generators 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +69 -0
- data/README.de.md +118 -4
- data/README.md +118 -4
- data/lib/b4um_generators/version.rb +1 -1
- data/lib/generators/b4um/admin/admin_generator.rb +141 -30
- data/lib/generators/b4um/authentication/authentication_generator.rb +346 -22
- data/lib/generators/b4um/comments/comments_generator.rb +39 -9
- data/lib/generators/b4um/help/help_generator.rb +89 -0
- data/lib/generators/b4um/in_place/in_place_generator.rb +132 -7
- data/lib/generators/b4um/install/templates/b4um/theme.css +23 -0
- data/lib/generators/b4um/readable/readable_generator.rb +8 -5
- data/lib/generators/b4um/scaffold/scaffold_generator.rb +163 -73
- data/lib/generators/b4um/sortable/sortable_generator.rb +104 -36
- data/lib/generators/b4um/todo/templates/_progress.html.erb.tt +22 -0
- data/lib/generators/b4um/todo/templates/_task.html.erb.tt +42 -0
- data/lib/generators/b4um/todo/templates/create_tasks.rb.tt +13 -0
- data/lib/generators/b4um/todo/templates/index.html.erb.tt +101 -0
- data/lib/generators/b4um/todo/templates/task.rb.tt +7 -0
- data/lib/generators/b4um/todo/templates/tasks_controller.rb.tt +190 -0
- data/lib/generators/b4um/todo/templates/todo.css +347 -0
- data/lib/generators/b4um/todo/templates/todo_controller.js +35 -0
- data/lib/generators/b4um/todo/todo_generator.rb +201 -0
- metadata +12 -3
|
@@ -198,7 +198,10 @@ module B4um
|
|
|
198
198
|
|
|
199
199
|
def add_authentication_helpers
|
|
200
200
|
controller_path = "app/controllers/application_controller.rb"
|
|
201
|
-
full_controller_path = File.join(
|
|
201
|
+
full_controller_path = File.join(
|
|
202
|
+
destination_root,
|
|
203
|
+
controller_path
|
|
204
|
+
)
|
|
202
205
|
|
|
203
206
|
unless File.exist?(full_controller_path)
|
|
204
207
|
raise Thor::Error,
|
|
@@ -211,31 +214,52 @@ module B4um
|
|
|
211
214
|
"helper_method :#{current_authentication_method}, :logged_in?"
|
|
212
215
|
)
|
|
213
216
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
authentication_helpers = [
|
|
218
|
+
"",
|
|
219
|
+
" helper_method :#{current_authentication_method}, :logged_in?",
|
|
220
|
+
"",
|
|
221
|
+
" private",
|
|
222
|
+
"",
|
|
223
|
+
" def #{current_authentication_method}",
|
|
224
|
+
" @#{current_authentication_method} ||= " \
|
|
225
|
+
"#{authentication_class_name}.find_by(" \
|
|
226
|
+
"id: session[:#{authentication_model_name}_id])",
|
|
227
|
+
" end",
|
|
228
|
+
"",
|
|
229
|
+
" def logged_in?",
|
|
230
|
+
" #{current_authentication_method}.present?",
|
|
231
|
+
" end",
|
|
232
|
+
"",
|
|
233
|
+
" def require_login",
|
|
234
|
+
" return if logged_in?",
|
|
235
|
+
"",
|
|
236
|
+
' redirect_to login_path, alert: "Please log in first."',
|
|
237
|
+
" end",
|
|
238
|
+
""
|
|
239
|
+
].join("\n")
|
|
240
|
+
|
|
241
|
+
class_end_position = controller.rindex(/^end\s*$/)
|
|
221
242
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
end
|
|
243
|
+
unless class_end_position
|
|
244
|
+
raise Thor::Error,
|
|
245
|
+
"Could not find the end of ApplicationController."
|
|
246
|
+
end
|
|
227
247
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
248
|
+
controller.insert(
|
|
249
|
+
class_end_position,
|
|
250
|
+
authentication_helpers
|
|
251
|
+
)
|
|
231
252
|
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
File.write(
|
|
254
|
+
full_controller_path,
|
|
255
|
+
controller
|
|
256
|
+
)
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
say_status(
|
|
259
|
+
:insert,
|
|
260
|
+
controller_path,
|
|
261
|
+
:green
|
|
262
|
+
)
|
|
239
263
|
end
|
|
240
264
|
|
|
241
265
|
def update_in_place_helper
|
|
@@ -503,6 +527,12 @@ module B4um
|
|
|
503
527
|
end
|
|
504
528
|
end
|
|
505
529
|
|
|
530
|
+
def update_controller_tests
|
|
531
|
+
update_authentication_fixtures
|
|
532
|
+
update_authentication_resource_controller_tests
|
|
533
|
+
update_protected_controller_tests
|
|
534
|
+
end
|
|
535
|
+
|
|
506
536
|
def protect_show_actions
|
|
507
537
|
protected_controller_names.each do |controller_name|
|
|
508
538
|
protect_show_actions_for(controller_name)
|
|
@@ -517,6 +547,300 @@ module B4um
|
|
|
517
547
|
|
|
518
548
|
private
|
|
519
549
|
|
|
550
|
+
def update_authentication_fixtures
|
|
551
|
+
fixture_path = File.join(
|
|
552
|
+
destination_root,
|
|
553
|
+
"test/fixtures",
|
|
554
|
+
"#{authentication_model_name.pluralize}.yml"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
return unless File.exist?(fixture_path)
|
|
558
|
+
|
|
559
|
+
fixtures = File.read(fixture_path)
|
|
560
|
+
|
|
561
|
+
fixture_name = nil
|
|
562
|
+
|
|
563
|
+
updated_fixtures = fixtures.lines.map do |line|
|
|
564
|
+
if (match = line.match(/^([a-zA-Z0-9_-]+):\s*$/))
|
|
565
|
+
fixture_name = match[1]
|
|
566
|
+
line
|
|
567
|
+
elsif line.match?(/^\s+email:\s*/)
|
|
568
|
+
fixture_label =
|
|
569
|
+
fixture_name
|
|
570
|
+
.to_s
|
|
571
|
+
.tr("_", "-")
|
|
572
|
+
|
|
573
|
+
indentation = line[/^\s*/]
|
|
574
|
+
|
|
575
|
+
"#{indentation}email: " \
|
|
576
|
+
"#{authentication_model_name}-#{fixture_label}@example.com\n"
|
|
577
|
+
elsif line.match?(/^\s+password_digest:\s*/)
|
|
578
|
+
indentation = line[/^\s*/]
|
|
579
|
+
|
|
580
|
+
"#{indentation}" \
|
|
581
|
+
'password_digest: <%= BCrypt::Password.create("password") %>' \
|
|
582
|
+
"\n"
|
|
583
|
+
else
|
|
584
|
+
line
|
|
585
|
+
end
|
|
586
|
+
end.join
|
|
587
|
+
|
|
588
|
+
return if updated_fixtures == fixtures
|
|
589
|
+
|
|
590
|
+
File.write(
|
|
591
|
+
fixture_path,
|
|
592
|
+
updated_fixtures
|
|
593
|
+
)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def update_authentication_resource_controller_tests
|
|
597
|
+
test_path = File.join(
|
|
598
|
+
"test/controllers",
|
|
599
|
+
"#{authentication_model_name.pluralize}_controller_test.rb"
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
full_test_path = File.join(
|
|
603
|
+
destination_root,
|
|
604
|
+
test_path
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
return unless File.exist?(full_test_path)
|
|
608
|
+
|
|
609
|
+
test = File.read(full_test_path)
|
|
610
|
+
|
|
611
|
+
update_authentication_test_password_params(
|
|
612
|
+
test_path,
|
|
613
|
+
test
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
test = File.read(full_test_path)
|
|
617
|
+
|
|
618
|
+
update_authentication_index_test(
|
|
619
|
+
test_path,
|
|
620
|
+
test
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
test = File.read(full_test_path)
|
|
624
|
+
|
|
625
|
+
add_authentication_sessions_to_resource_tests(
|
|
626
|
+
test_path,
|
|
627
|
+
test
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
test = File.read(full_test_path)
|
|
631
|
+
|
|
632
|
+
update_authentication_destroy_redirect_test(
|
|
633
|
+
test_path,
|
|
634
|
+
test
|
|
635
|
+
)
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def update_protected_controller_tests
|
|
639
|
+
protected_controller_names.each do |controller_name|
|
|
640
|
+
test_path = File.join(
|
|
641
|
+
"test/controllers",
|
|
642
|
+
"#{controller_name.underscore}_controller_test.rb"
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
full_test_path = File.join(
|
|
646
|
+
destination_root,
|
|
647
|
+
test_path
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
next unless File.exist?(full_test_path)
|
|
651
|
+
|
|
652
|
+
add_login_to_protected_controller_test(
|
|
653
|
+
test_path,
|
|
654
|
+
controller_name
|
|
655
|
+
)
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def update_authentication_test_password_params(test_path, test)
|
|
660
|
+
return unless test.include?(
|
|
661
|
+
"password_digest: @#{authentication_model_name}.password_digest"
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
gsub_file(
|
|
665
|
+
test_path,
|
|
666
|
+
"password_digest: @#{authentication_model_name}.password_digest",
|
|
667
|
+
'password: "password", password_confirmation: "password"'
|
|
668
|
+
)
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def update_authentication_index_test(test_path, test)
|
|
672
|
+
resource_name = authentication_model_name
|
|
673
|
+
plural_name = resource_name.pluralize
|
|
674
|
+
test_name = Regexp.escape("should get index")
|
|
675
|
+
|
|
676
|
+
pattern = /
|
|
677
|
+
^[ \t]*test\s+"#{test_name}"\s+do
|
|
678
|
+
\s+get\s+#{Regexp.escape(plural_name)}_url
|
|
679
|
+
\s+assert_response\s+:success
|
|
680
|
+
\s+end
|
|
681
|
+
/x
|
|
682
|
+
|
|
683
|
+
return unless test.match?(pattern)
|
|
684
|
+
|
|
685
|
+
replacement = [
|
|
686
|
+
" test \"should get index\" do",
|
|
687
|
+
" log_in_as(@#{resource_name})",
|
|
688
|
+
"",
|
|
689
|
+
" get #{plural_name}_url",
|
|
690
|
+
"",
|
|
691
|
+
" assert_redirected_to " \
|
|
692
|
+
"#{resource_name}_url(@#{resource_name})",
|
|
693
|
+
" end"
|
|
694
|
+
].join("\n")
|
|
695
|
+
|
|
696
|
+
gsub_file(
|
|
697
|
+
test_path,
|
|
698
|
+
pattern,
|
|
699
|
+
replacement
|
|
700
|
+
)
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def add_authentication_sessions_to_resource_tests(test_path, test)
|
|
704
|
+
%w[show edit update destroy].each do |action|
|
|
705
|
+
test_name =
|
|
706
|
+
if action == "edit"
|
|
707
|
+
"should get edit"
|
|
708
|
+
else
|
|
709
|
+
"should #{action} #{authentication_model_name}"
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
pattern = /
|
|
713
|
+
(test\s+"#{Regexp.escape(test_name)}"\s+do\n)
|
|
714
|
+
(?!\s*log_in_as\(@#{Regexp.escape(authentication_model_name)}\))
|
|
715
|
+
/x
|
|
716
|
+
|
|
717
|
+
next unless test.match?(pattern)
|
|
718
|
+
|
|
719
|
+
gsub_file(test_path, pattern) do |match|
|
|
720
|
+
"#{match} log_in_as(@#{authentication_model_name})\n"
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
test = File.read(
|
|
724
|
+
File.join(destination_root, test_path)
|
|
725
|
+
)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
add_login_helper_to_controller_test(test_path)
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
def add_login_helper_to_controller_test(test_path)
|
|
732
|
+
full_test_path = File.join(
|
|
733
|
+
destination_root,
|
|
734
|
+
test_path
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
test = File.read(full_test_path)
|
|
738
|
+
|
|
739
|
+
return if test.include?("def log_in_as(user)")
|
|
740
|
+
|
|
741
|
+
final_end = test.rindex(/^end$/)
|
|
742
|
+
return unless final_end
|
|
743
|
+
|
|
744
|
+
helper =
|
|
745
|
+
"\n private\n\n " \
|
|
746
|
+
"def log_in_as(user)\n " \
|
|
747
|
+
'user.update!(password: "password", password_confirmation: "password")' \
|
|
748
|
+
"\n " \
|
|
749
|
+
'post login_url, params: { email: user.email, password: "password" }' \
|
|
750
|
+
"\n " \
|
|
751
|
+
"end\n"
|
|
752
|
+
|
|
753
|
+
updated_test =
|
|
754
|
+
test.dup.insert(final_end, helper)
|
|
755
|
+
|
|
756
|
+
File.write(
|
|
757
|
+
full_test_path,
|
|
758
|
+
updated_test
|
|
759
|
+
)
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def update_authentication_destroy_redirect_test(test_path, test)
|
|
763
|
+
plural_name = authentication_model_name.pluralize
|
|
764
|
+
|
|
765
|
+
old_redirect =
|
|
766
|
+
"assert_redirected_to #{plural_name}_url"
|
|
767
|
+
|
|
768
|
+
return unless test.include?(old_redirect)
|
|
769
|
+
|
|
770
|
+
gsub_file(
|
|
771
|
+
test_path,
|
|
772
|
+
old_redirect,
|
|
773
|
+
"assert_redirected_to root_url"
|
|
774
|
+
)
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
def add_login_to_protected_controller_test(
|
|
778
|
+
test_path,
|
|
779
|
+
controller_name
|
|
780
|
+
)
|
|
781
|
+
full_test_path = File.join(
|
|
782
|
+
destination_root,
|
|
783
|
+
test_path
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
test = File.read(full_test_path)
|
|
787
|
+
|
|
788
|
+
add_authentication_fixture_to_test(
|
|
789
|
+
test_path,
|
|
790
|
+
test
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
test = File.read(full_test_path)
|
|
794
|
+
|
|
795
|
+
protected_test_actions_for(controller_name).each do |test_name|
|
|
796
|
+
pattern = /
|
|
797
|
+
(test\s+"#{Regexp.escape(test_name)}"\s+do\n)
|
|
798
|
+
(?!\s*log_in_as\(@authentication_user\))
|
|
799
|
+
/x
|
|
800
|
+
|
|
801
|
+
next unless test.match?(pattern)
|
|
802
|
+
|
|
803
|
+
gsub_file(test_path, pattern) do |match|
|
|
804
|
+
"#{match} log_in_as(@authentication_user)\n"
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
test = File.read(full_test_path)
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
add_login_helper_to_controller_test(test_path)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def add_authentication_fixture_to_test(test_path, test)
|
|
814
|
+
fixture_assignment =
|
|
815
|
+
"@authentication_user = " \
|
|
816
|
+
"#{authentication_model_name.pluralize}(:one)"
|
|
817
|
+
|
|
818
|
+
return if test.include?(fixture_assignment)
|
|
819
|
+
|
|
820
|
+
setup_pattern = /
|
|
821
|
+
(setup\s+do\n)
|
|
822
|
+
/x
|
|
823
|
+
|
|
824
|
+
return unless test.match?(setup_pattern)
|
|
825
|
+
|
|
826
|
+
gsub_file(test_path, setup_pattern) do |match|
|
|
827
|
+
"#{match} #{fixture_assignment}\n"
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def protected_test_actions_for(controller_name)
|
|
832
|
+
singular_name =
|
|
833
|
+
controller_name.underscore.singularize
|
|
834
|
+
|
|
835
|
+
[
|
|
836
|
+
"should get new",
|
|
837
|
+
"should create #{singular_name}",
|
|
838
|
+
"should get edit",
|
|
839
|
+
"should update #{singular_name}",
|
|
840
|
+
"should destroy #{singular_name}"
|
|
841
|
+
]
|
|
842
|
+
end
|
|
843
|
+
|
|
520
844
|
def add_register_navigation_link(navigation_path, navigation)
|
|
521
845
|
return if navigation.include?(
|
|
522
846
|
'navigation_link_to "Register",'
|
|
@@ -169,20 +169,50 @@ module B4um
|
|
|
169
169
|
routes = File.read(full_routes_path)
|
|
170
170
|
resource_name = commentable.to_s.underscore.pluralize
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
resource_block = /
|
|
173
|
+
^(?<indent>[ \t]*)resources\s+:#{Regexp.escape(resource_name)}\s+do[ \t]*\n
|
|
174
|
+
(?<body>.*?)
|
|
175
|
+
^\k<indent>end[ \t]*$
|
|
176
|
+
/mx
|
|
177
|
+
|
|
178
|
+
if (match = routes.match(resource_block))
|
|
179
|
+
return if match[:body].include?(
|
|
180
|
+
"resources :comments, only: %i[create destroy]"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
updated_block = [
|
|
184
|
+
"#{match[:indent]}resources :#{resource_name} do",
|
|
185
|
+
match[:body].rstrip,
|
|
186
|
+
"#{match[:indent]} resources :comments, only: %i[create destroy]",
|
|
187
|
+
"#{match[:indent]}end"
|
|
188
|
+
].reject(&:empty?).join("\n")
|
|
189
|
+
|
|
190
|
+
gsub_file(
|
|
191
|
+
routes_path,
|
|
192
|
+
match[0],
|
|
193
|
+
updated_block
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
simple_resource =
|
|
200
|
+
/^([ \t]*)resources\s+:#{Regexp.escape(resource_name)}[ \t]*$/
|
|
201
|
+
|
|
202
|
+
return unless routes.match?(simple_resource)
|
|
175
203
|
|
|
176
204
|
gsub_file(
|
|
177
205
|
routes_path,
|
|
178
|
-
|
|
206
|
+
simple_resource
|
|
207
|
+
) do |resource_line|
|
|
208
|
+
indentation = resource_line[/\A[ \t]*/]
|
|
209
|
+
|
|
179
210
|
[
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
""
|
|
211
|
+
"#{indentation}resources :#{resource_name} do",
|
|
212
|
+
"#{indentation} resources :comments, only: %i[create destroy]",
|
|
213
|
+
"#{indentation}end"
|
|
184
214
|
].join("\n")
|
|
185
|
-
|
|
215
|
+
end
|
|
186
216
|
end
|
|
187
217
|
|
|
188
218
|
def add_comments_to_show_view
|
|
@@ -1172,6 +1172,95 @@ module B4um
|
|
|
1172
1172
|
- Safe repeated generator runs
|
|
1173
1173
|
|
|
1174
1174
|
|
|
1175
|
+
TODO
|
|
1176
|
+
|
|
1177
|
+
bin/rails generate b4um:todo MODEL
|
|
1178
|
+
|
|
1179
|
+
Generates a complete Todo application for Rails.
|
|
1180
|
+
|
|
1181
|
+
Example:
|
|
1182
|
+
|
|
1183
|
+
bin/rails generate b4um:todo Task
|
|
1184
|
+
bin/rails db:migrate
|
|
1185
|
+
|
|
1186
|
+
By default, the Todo list is shared across the application.
|
|
1187
|
+
Tasks are not assigned to individual users.
|
|
1188
|
+
|
|
1189
|
+
To create personal Todo lists, use --owner:
|
|
1190
|
+
|
|
1191
|
+
bin/rails generate b4um:todo Task --owner=User
|
|
1192
|
+
bin/rails db:migrate
|
|
1193
|
+
|
|
1194
|
+
The owner model must already exist.
|
|
1195
|
+
|
|
1196
|
+
For user-specific Todo lists, b4um authentication can be
|
|
1197
|
+
installed first:
|
|
1198
|
+
|
|
1199
|
+
bin/rails generate b4um:scaffold User name:string email:string password_digest:string
|
|
1200
|
+
bin/rails db:migrate
|
|
1201
|
+
bin/rails generate b4um:authentication User
|
|
1202
|
+
bin/rails generate b4um:todo Task --owner=User
|
|
1203
|
+
bin/rails db:migrate
|
|
1204
|
+
|
|
1205
|
+
The owner option supports other authentication models:
|
|
1206
|
+
|
|
1207
|
+
--owner=User
|
|
1208
|
+
--owner=Member
|
|
1209
|
+
--owner=Customer
|
|
1210
|
+
|
|
1211
|
+
The generated controller uses the corresponding
|
|
1212
|
+
current owner helper, for example:
|
|
1213
|
+
|
|
1214
|
+
current_user
|
|
1215
|
+
current_member
|
|
1216
|
+
current_customer
|
|
1217
|
+
|
|
1218
|
+
With an owner, each account has its own Todo list.
|
|
1219
|
+
|
|
1220
|
+
Tasks are loaded through the current owner's association.
|
|
1221
|
+
Users cannot access tasks belonging to another account.
|
|
1222
|
+
|
|
1223
|
+
Unauthenticated requests to owner-specific Todo actions
|
|
1224
|
+
receive HTTP 401 Unauthorized.
|
|
1225
|
+
|
|
1226
|
+
Todo features:
|
|
1227
|
+
|
|
1228
|
+
- Create new tasks
|
|
1229
|
+
- Mark tasks as completed
|
|
1230
|
+
- Reopen completed tasks
|
|
1231
|
+
- Delete tasks
|
|
1232
|
+
- Separate lists for open and completed tasks
|
|
1233
|
+
- Automatic task counters
|
|
1234
|
+
- Progress bar with completion percentage
|
|
1235
|
+
- Turbo Stream updates without full page reloads
|
|
1236
|
+
- Stimulus controller for the task input
|
|
1237
|
+
- Automatic input reset after successful creation
|
|
1238
|
+
- b4um floating labels
|
|
1239
|
+
- Responsive b4um card layout
|
|
1240
|
+
- Light and dark mode support
|
|
1241
|
+
- Optional owner association with --owner
|
|
1242
|
+
- Owner-specific task access protection
|
|
1243
|
+
|
|
1244
|
+
The generator creates:
|
|
1245
|
+
|
|
1246
|
+
- Task model and database migration
|
|
1247
|
+
- Tasks controller
|
|
1248
|
+
- Resource routes
|
|
1249
|
+
- Index view
|
|
1250
|
+
- Task partial
|
|
1251
|
+
- Progress partial
|
|
1252
|
+
- Todo stylesheet
|
|
1253
|
+
- Stimulus controller
|
|
1254
|
+
|
|
1255
|
+
After generating the Todo application, run:
|
|
1256
|
+
|
|
1257
|
+
bin/rails db:migrate
|
|
1258
|
+
|
|
1259
|
+
With the default Task model, the Todo list is available at:
|
|
1260
|
+
|
|
1261
|
+
/tasks
|
|
1262
|
+
|
|
1263
|
+
|
|
1175
1264
|
COMMENTS
|
|
1176
1265
|
|
|
1177
1266
|
bin/rails generate b4um:comments MODEL
|
|
@@ -137,9 +137,48 @@ module B4um
|
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
def add_edit_actions
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
controller_path = File.join(
|
|
141
|
+
"app/controllers",
|
|
142
|
+
"#{plural_table_name}_controller.rb"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
full_controller_path = File.join(
|
|
146
|
+
destination_root,
|
|
147
|
+
controller_path
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
controller = File.read(full_controller_path)
|
|
151
|
+
|
|
152
|
+
fields_to_insert =
|
|
153
|
+
prepare_in_place_edit_actions(controller)
|
|
154
|
+
|
|
155
|
+
create_comment =
|
|
156
|
+
extract_create_comment(controller)
|
|
157
|
+
|
|
158
|
+
create_match =
|
|
159
|
+
find_create_action(controller, controller_path)
|
|
160
|
+
|
|
161
|
+
replacement = build_edit_actions_block(
|
|
162
|
+
fields_to_insert,
|
|
163
|
+
create_match[1],
|
|
164
|
+
create_comment
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
controller.sub!(
|
|
168
|
+
/\n*^[ \t]*def[ \t]+create[ \t]*$/,
|
|
169
|
+
replacement
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
File.write(
|
|
173
|
+
full_controller_path,
|
|
174
|
+
controller
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
say_status(
|
|
178
|
+
:gsub,
|
|
179
|
+
controller_path,
|
|
180
|
+
:green
|
|
181
|
+
)
|
|
143
182
|
end
|
|
144
183
|
|
|
145
184
|
def add_edit_actions_to_set_product
|
|
@@ -216,6 +255,77 @@ module B4um
|
|
|
216
255
|
|
|
217
256
|
private
|
|
218
257
|
|
|
258
|
+
def prepare_in_place_edit_actions(controller)
|
|
259
|
+
fields.filter_map do |field|
|
|
260
|
+
empty_edit_action = /
|
|
261
|
+
^[ \t]*def[ \t]+edit_#{Regexp.escape(field)}[ \t]*\n
|
|
262
|
+
^[ \t]*end[ \t]*\n?
|
|
263
|
+
/x
|
|
264
|
+
|
|
265
|
+
removed_empty_action =
|
|
266
|
+
controller.gsub!(empty_edit_action, "")
|
|
267
|
+
|
|
268
|
+
custom_action =
|
|
269
|
+
controller.match?(
|
|
270
|
+
/^[ \t]*def[ \t]+edit_#{Regexp.escape(field)}[ \t]*$/
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
field unless custom_action && !removed_empty_action
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def extract_create_comment(controller)
|
|
278
|
+
create_comment_pattern =
|
|
279
|
+
%r{^[ \t]*# POST /#{Regexp.escape(plural_table_name)} or /#{Regexp.escape(plural_table_name)}\.json[ \t]*\n?}
|
|
280
|
+
|
|
281
|
+
create_comment =
|
|
282
|
+
controller[create_comment_pattern]&.rstrip
|
|
283
|
+
|
|
284
|
+
controller.gsub!(
|
|
285
|
+
create_comment_pattern,
|
|
286
|
+
""
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
create_comment
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def find_create_action(controller, controller_path)
|
|
293
|
+
create_match =
|
|
294
|
+
controller.match(
|
|
295
|
+
/^([ \t]*)def[ \t]+create[ \t]*$/
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return create_match if create_match
|
|
299
|
+
|
|
300
|
+
raise Thor::Error,
|
|
301
|
+
"Could not find create action in #{controller_path}"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def build_edit_actions_block(
|
|
305
|
+
fields_to_insert,
|
|
306
|
+
indentation,
|
|
307
|
+
create_comment
|
|
308
|
+
)
|
|
309
|
+
edit_actions = fields_to_insert.flat_map do |field|
|
|
310
|
+
[
|
|
311
|
+
"#{indentation}def edit_#{field}",
|
|
312
|
+
"#{indentation}end",
|
|
313
|
+
""
|
|
314
|
+
]
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
replacement = [
|
|
318
|
+
"",
|
|
319
|
+
"",
|
|
320
|
+
*edit_actions
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
replacement << create_comment if create_comment
|
|
324
|
+
replacement << "#{indentation}def create"
|
|
325
|
+
|
|
326
|
+
replacement.join("\n")
|
|
327
|
+
end
|
|
328
|
+
|
|
219
329
|
def ensure_flash_render_in_layout
|
|
220
330
|
layout_path = "app/views/layouts/application.html.erb"
|
|
221
331
|
full_layout_path = File.join(destination_root, layout_path)
|
|
@@ -581,11 +691,26 @@ module B4um
|
|
|
581
691
|
/^\s*def edit_#{Regexp.escape(field)}\s*$/
|
|
582
692
|
)
|
|
583
693
|
|
|
584
|
-
|
|
694
|
+
create_action = /^([ \t]*)def create\s*$/
|
|
695
|
+
|
|
696
|
+
unless controller.match?(create_action)
|
|
697
|
+
raise Thor::Error,
|
|
698
|
+
"Could not find create action in #{controller_path}"
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
gsub_file(
|
|
585
702
|
controller_path,
|
|
586
|
-
|
|
587
|
-
) do
|
|
588
|
-
|
|
703
|
+
/\n*[ \t]*def create\s*$/
|
|
704
|
+
) do |match|
|
|
705
|
+
indentation = match[/[ \t]*(?=def create)/]
|
|
706
|
+
|
|
707
|
+
[
|
|
708
|
+
"",
|
|
709
|
+
"#{indentation}def edit_#{field}",
|
|
710
|
+
"#{indentation}end",
|
|
711
|
+
"",
|
|
712
|
+
"#{indentation}def create"
|
|
713
|
+
].join("\n")
|
|
589
714
|
end
|
|
590
715
|
end
|
|
591
716
|
|