uchi 0.1.0 → 0.1.2

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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +17 -0
  3. data/.github/workflows/build.yml +23 -0
  4. data/.github/workflows/lint.yml +30 -0
  5. data/package.json +31 -0
  6. data/test/components/uchi/field/belongs_to_test.rb +134 -0
  7. data/test/components/uchi/field/blank_test.rb +119 -0
  8. data/test/components/uchi/field/boolean_test.rb +163 -0
  9. data/test/components/uchi/field/date_test.rb +163 -0
  10. data/test/components/uchi/field/date_time_test.rb +152 -0
  11. data/test/components/uchi/field/has_many_test.rb +138 -0
  12. data/test/components/uchi/field/id_test.rb +113 -0
  13. data/test/components/uchi/field/number_test.rb +163 -0
  14. data/test/components/uchi/field/string_test.rb +159 -0
  15. data/test/controllers/uchi/authors_controller_test.rb +119 -0
  16. data/test/controllers/uchi/repository_controller_test.rb +93 -0
  17. data/test/controllers/uchi/scoped_repository_controller_test.rb +73 -0
  18. data/test/dummy/Rakefile +6 -0
  19. data/test/dummy/app/assets/images/.keep +0 -0
  20. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  21. data/test/dummy/app/controllers/application_controller.rb +4 -0
  22. data/test/dummy/app/controllers/concerns/.keep +0 -0
  23. data/test/dummy/app/controllers/uchi/authors_controller.rb +7 -0
  24. data/test/dummy/app/controllers/uchi/books_controller.rb +7 -0
  25. data/test/dummy/app/controllers/uchi/titles_controller.rb +7 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/jobs/application_job.rb +7 -0
  28. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  29. data/test/dummy/app/models/application_record.rb +3 -0
  30. data/test/dummy/app/models/author.rb +3 -0
  31. data/test/dummy/app/models/book.rb +3 -0
  32. data/test/dummy/app/models/concerns/.keep +0 -0
  33. data/test/dummy/app/models/title.rb +3 -0
  34. data/test/dummy/app/uchi/repositories/author.rb +20 -0
  35. data/test/dummy/app/uchi/repositories/book.rb +16 -0
  36. data/test/dummy/app/uchi/repositories/title.rb +17 -0
  37. data/test/dummy/app/views/layouts/application.html.erb +27 -0
  38. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  39. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  40. data/test/dummy/app/views/pwa/manifest.json.erb +22 -0
  41. data/test/dummy/app/views/pwa/service-worker.js +26 -0
  42. data/test/dummy/bin/dev +2 -0
  43. data/test/dummy/bin/rails +4 -0
  44. data/test/dummy/bin/rake +4 -0
  45. data/test/dummy/bin/setup +34 -0
  46. data/test/dummy/config/application.rb +29 -0
  47. data/test/dummy/config/boot.rb +5 -0
  48. data/test/dummy/config/cable.yml +10 -0
  49. data/test/dummy/config/database.yml +32 -0
  50. data/test/dummy/config/environment.rb +5 -0
  51. data/test/dummy/config/environments/development.rb +69 -0
  52. data/test/dummy/config/environments/production.rb +89 -0
  53. data/test/dummy/config/environments/test.rb +53 -0
  54. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  56. data/test/dummy/config/initializers/inflections.rb +16 -0
  57. data/test/dummy/config/locales/da.yml +51 -0
  58. data/test/dummy/config/locales/en.yml +31 -0
  59. data/test/dummy/config/puma.rb +38 -0
  60. data/test/dummy/config/routes.rb +9 -0
  61. data/test/dummy/config/storage.yml +34 -0
  62. data/test/dummy/config.ru +6 -0
  63. data/test/dummy/db/migrate/20251002183635_create_authors.rb +11 -0
  64. data/test/dummy/db/migrate/20251005131726_create_books.rb +9 -0
  65. data/test/dummy/db/migrate/20251005131811_create_titles.rb +11 -0
  66. data/test/dummy/db/schema.rb +38 -0
  67. data/test/dummy/log/.keep +0 -0
  68. data/test/dummy/public/400.html +114 -0
  69. data/test/dummy/public/404.html +114 -0
  70. data/test/dummy/public/406-unsupported-browser.html +114 -0
  71. data/test/dummy/public/422.html +114 -0
  72. data/test/dummy/public/500.html +114 -0
  73. data/test/dummy/public/icon.png +0 -0
  74. data/test/dummy/public/icon.svg +3 -0
  75. data/test/dummy/storage/.keep +0 -0
  76. data/test/dummy/test/fixtures/authors.yml +11 -0
  77. data/test/dummy/test/models/author_test.rb +7 -0
  78. data/test/dummy/tmp/.keep +0 -0
  79. data/test/dummy/tmp/pids/.keep +0 -0
  80. data/test/dummy/tmp/storage/.keep +0 -0
  81. data/test/test_helper.rb +15 -0
  82. data/test/uchi/field_test.rb +63 -0
  83. data/test/uchi/i18n_test.rb +18 -0
  84. data/test/uchi/repository/routes_test.rb +49 -0
  85. data/test/uchi/repository/translate_test.rb +263 -0
  86. data/test/uchi/repository_test.rb +137 -0
  87. data/test/uchi/sort_order_test.rb +47 -0
  88. data/test/uchi_test.rb +7 -0
  89. metadata +146 -7
  90. data/README.md +0 -35
  91. data/Rakefile +0 -6
  92. data/lib/uchi/version.rb +0 -5
  93. data/lib/uchi.rb +0 -8
@@ -0,0 +1,159 @@
1
+ require "test_helper"
2
+ require "ostruct"
3
+
4
+ module Uchi
5
+ class Field
6
+ class StringTest < ActiveSupport::TestCase
7
+ def setup
8
+ @field = Uchi::Field::String.new(:name)
9
+ @form = OpenStruct.new(object: OpenStruct.new(name: "Test Name"))
10
+ @repository = Uchi::Repositories::Author.new
11
+ end
12
+
13
+ test "inherits from Uchi::Field" do
14
+ assert_kind_of Uchi::Field, @field
15
+ end
16
+
17
+ test "has default options" do
18
+ assert_equal [:edit, :index, :show], @field.on
19
+ assert @field.searchable?
20
+ assert @field.sortable?
21
+ end
22
+
23
+ test "#edit_component returns an instance of Edit component" do
24
+ component = @field.edit_component(form: @form, hint: "Custom hint", label: "Custom label", repository: @repository)
25
+ assert_equal "Custom hint", component.hint
26
+ assert_equal "Custom label", component.label
27
+ assert_equal @field, component.field
28
+ assert_equal @form, component.form
29
+ assert_equal @repository, component.repository
30
+ assert_kind_of Uchi::Field::String::Edit, component
31
+ end
32
+
33
+ test "#index_component returns an instance of Index component" do
34
+ component = @field.index_component(record: @form.object, repository: @repository)
35
+ assert_equal @field, component.field
36
+ assert_equal @form.object, component.record
37
+ assert_equal @repository, component.repository
38
+ assert_kind_of Uchi::Field::String::Index, component
39
+ end
40
+
41
+ test "#searchable? returns false when explicitly set" do
42
+ field = Uchi::Field::String.new(:name, searchable: false)
43
+ assert_not field.searchable?
44
+ end
45
+
46
+ test "#show_component returns an instance of Show component" do
47
+ component = @field.show_component(record: @form.object, repository: @repository)
48
+ assert_equal @field, component.field
49
+ assert_equal @form.object, component.record
50
+ assert_equal @repository, component.repository
51
+ assert_kind_of Uchi::Field::String::Show, component
52
+ end
53
+
54
+ test "#sortable? returns false when explicitly set" do
55
+ field = Uchi::Field::String.new(:name, sortable: false)
56
+ assert_not field.sortable?
57
+ end
58
+ end
59
+
60
+ class StringEditTest < ViewComponent::TestCase
61
+ def setup
62
+ @field = Uchi::Field::String.new(:name)
63
+ @record = Author.new(name: "J.R.R Tolkien")
64
+ @repository = Uchi::Repositories::Author.new
65
+ @view_context = ActionController::Base.new.view_context
66
+
67
+ @form = ActionView::Helpers::FormBuilder.new(:author, @record, @view_context, {})
68
+
69
+ @component = Uchi::Field::String::Edit.new(
70
+ field: @field,
71
+ form: @form,
72
+ hint: "Custom hint",
73
+ label: "Custom label",
74
+ repository: @repository
75
+ )
76
+ end
77
+
78
+ test "inherits from Base component" do
79
+ assert_kind_of Uchi::Field::Base::Edit, @component
80
+ end
81
+
82
+ test "renders an input field with the field content" do
83
+ render_inline(@component)
84
+
85
+ assert_selector("input[type='text'][name='author[name]'][value='J.R.R Tolkien']")
86
+ end
87
+
88
+ test "renders label with specified text" do
89
+ render_inline(@component)
90
+
91
+ assert_selector("label[for='author_name']", text: "Custom label")
92
+ end
93
+
94
+ test "renders hint when provided" do
95
+ render_inline(@component)
96
+
97
+ assert_selector("p[id=author_name_hint]", text: "Custom hint")
98
+ end
99
+
100
+ test "initializes the input component with the correct options" do
101
+ expected_options = {
102
+ attribute: :name,
103
+ form: @form,
104
+ label: {content: "Custom label"},
105
+ hint: {content: "Custom hint"}
106
+ }
107
+ assert_equal expected_options, @component.send(:options)
108
+ end
109
+ end
110
+
111
+ class StringIndexTest < ViewComponent::TestCase
112
+ def setup
113
+ @field = Uchi::Field::String.new(:name)
114
+ @record = Author.new(name: "J.R.R Tolkien")
115
+ @repository = Uchi::Repositories::Author.new
116
+
117
+ @component = Uchi::Field::String::Index.new(
118
+ field: @field,
119
+ record: @record,
120
+ repository: @repository
121
+ )
122
+ end
123
+
124
+ test "inherits from Base component" do
125
+ assert_kind_of Uchi::Field::Base::Index, @component
126
+ end
127
+
128
+ test "renders the field content" do
129
+ result = render_inline(@component)
130
+
131
+ assert_includes result.to_html, "J.R.R Tolkien"
132
+ end
133
+ end
134
+
135
+ class StringShowTest < ViewComponent::TestCase
136
+ def setup
137
+ @field = Uchi::Field::String.new(:name)
138
+ @record = Author.new(name: "J.R.R Tolkien")
139
+ @repository = Uchi::Repositories::Author.new
140
+
141
+ @component = Uchi::Field::String::Show.new(
142
+ field: @field,
143
+ record: @record,
144
+ repository: @repository
145
+ )
146
+ end
147
+
148
+ test "inherits from Base component" do
149
+ assert_kind_of Uchi::Field::Base::Show, @component
150
+ end
151
+
152
+ test "renders the field content" do
153
+ result = render_inline(@component)
154
+
155
+ assert_includes result.to_html, "J.R.R Tolkien"
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,119 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class AuthorsControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @author = Author.create!(name: "Test Author")
7
+ end
8
+
9
+ test "GET edit renders successfully" do
10
+ get edit_uchi_author_url(id: @author.id)
11
+ assert_response :success
12
+ end
13
+
14
+ test "GET edit renders the edit view" do
15
+ get edit_uchi_author_url(id: @author.id)
16
+ assert_template :edit
17
+ end
18
+
19
+ test "GET index responds successfully" do
20
+ get uchi_authors_url
21
+ assert_response :success
22
+ end
23
+
24
+ test "GET index renders the index view" do
25
+ get uchi_authors_url
26
+ assert_template :index
27
+ end
28
+
29
+ test "GET new renders successfully" do
30
+ get new_uchi_author_url
31
+ assert_response :success
32
+ end
33
+
34
+ test "GET new renders the new view" do
35
+ get new_uchi_author_url
36
+ assert_template :new
37
+ end
38
+
39
+ test "GET show responds successfully" do
40
+ get uchi_author_url(id: @author.id)
41
+ assert_response :success
42
+ end
43
+
44
+ test "GET show renders the show view" do
45
+ get uchi_author_url(id: @author.id)
46
+ assert_template :show
47
+ end
48
+
49
+ test "PATCH update redirects to show after successful update" do
50
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
51
+ assert_redirected_to uchi_author_url(id: @author.id)
52
+ end
53
+
54
+ test "PATCH update responds with 303 after successful update" do
55
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
56
+ assert_response :see_other
57
+ end
58
+
59
+ test "PATCH update changes the author's attributes" do
60
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
61
+ @author.reload
62
+ assert_equal "Updated Name", @author.name
63
+ end
64
+
65
+ test "PATCH update flashes a translated success message after successful update" do
66
+ ::I18n.with_locale(:da) do
67
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
68
+ assert_equal "Dine ændringer til forfatteren blev gemt", flash[:notice]
69
+ end
70
+ end
71
+
72
+ test "PATCH update falls back to default success message after successful update" do
73
+ patch uchi_author_url(id: @author.id), params: {author: {name: "Updated Name"}}
74
+ assert_equal "Your changes have been saved", flash[:notice]
75
+ end
76
+
77
+ test "PATCH update rerenders the edit view after unsuccessful update" do
78
+ patch uchi_author_url(id: @author.id), params: {author: {name: ""}}
79
+ assert_template :edit
80
+ end
81
+
82
+ test "PATCH responds with 422 after unsuccessful update" do
83
+ patch uchi_author_url(id: @author.id), params: {author: {name: ""}}
84
+ assert_response :unprocessable_entity
85
+ end
86
+
87
+ test "POST create redirects to show after successful creation" do
88
+ post uchi_authors_url, params: {author: {name: "New Author"}}
89
+ assert_redirected_to uchi_author_url(id: Author.last.id)
90
+ end
91
+
92
+ test "POST create responds with 303 after successful creation" do
93
+ post uchi_authors_url, params: {author: {name: "New Author"}}
94
+ assert_response :see_other
95
+ end
96
+
97
+ test "POST create flashes a translated success message after successful creation" do
98
+ ::I18n.with_locale(:da) do
99
+ post uchi_authors_url, params: {author: {name: "New Author"}}
100
+ assert_equal "Forfatteren er blevet tilføjet", flash[:notice]
101
+ end
102
+ end
103
+
104
+ test "POST create creates a new author" do
105
+ post uchi_authors_url, params: {author: {name: "New Author"}}
106
+ assert_equal "New Author", Author.last.name
107
+ end
108
+
109
+ test "POST create rerenders the new view after unsuccessful creation" do
110
+ post uchi_authors_url, params: {author: {name: ""}}
111
+ assert_template :new
112
+ end
113
+
114
+ test "POST create responds with 422 after unsuccessful creation" do
115
+ post uchi_authors_url, params: {author: {name: ""}}
116
+ assert_response :unprocessable_entity
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,93 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class RepositoryControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @book = Book.create!(original_title: "The Hobbit")
7
+ end
8
+
9
+ test "DELETE destroy deletes the record" do
10
+ delete uchi_book_url(id: @book.id)
11
+
12
+ assert_not Book.exists?(@book.id)
13
+ end
14
+
15
+ test "DELETE destroy redirects to index" do
16
+ delete uchi_book_url(id: @book.id)
17
+
18
+ assert_redirected_to uchi_books_url
19
+ end
20
+
21
+ test "GET edit responds successfully" do
22
+ get edit_uchi_book_url(id: @book.id)
23
+ assert_response :success
24
+ end
25
+
26
+ test "GET edit renders a form for updating the model" do
27
+ get edit_uchi_book_url(id: @book.id)
28
+ assert_select "form[action=?][method='post']", uchi_book_path(id: @book.id)
29
+ end
30
+
31
+ test "GET edit links back to the model" do
32
+ get edit_uchi_book_url(id: @book.id)
33
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
34
+ end
35
+
36
+ test "GET index links to show for each record" do
37
+ get uchi_books_url
38
+
39
+ assert_select "tr td a[data-turbo-frame='_top'][href=?]", uchi_book_path(id: @book.id)
40
+ end
41
+
42
+ test "GET index links to edit for each record" do
43
+ get uchi_books_url
44
+ assert_select "tr td a[data-turbo-frame='_top'][href=?]", edit_uchi_book_path(id: @book.id)
45
+ end
46
+
47
+ test "GET new responds successfully" do
48
+ get new_uchi_book_url
49
+ assert_response :success
50
+ end
51
+
52
+ test "GET new renders a form to create a new model" do
53
+ get new_uchi_book_url
54
+ assert_select "form[action=?][method='post']", uchi_books_path
55
+ end
56
+
57
+ test "GET new links back to the index" do
58
+ get new_uchi_book_url
59
+ assert_select "a[href=?]", uchi_books_path, text: "Cancel"
60
+ end
61
+
62
+ test "GET show responds successfully" do
63
+ get uchi_book_url(id: @book.id)
64
+ assert_response :success
65
+ end
66
+
67
+ test "GET show renders the show view" do
68
+ get uchi_book_url(id: @book.id)
69
+ assert_template :show
70
+ end
71
+
72
+ test "GET show includes a button to delete the record" do
73
+ get uchi_book_url(id: @book.id)
74
+ assert_select "form[action=?]", uchi_book_path(id: @book.id), text: "Delete"
75
+ end
76
+
77
+ test "GET show includes a turbo-frame that loads the titles in a scoped index view" do
78
+ get uchi_book_url(id: @book.id)
79
+
80
+ assert_select "turbo-frame[src]" do |node_set|
81
+ tag = node_set.first
82
+ src = tag.attributes["src"].value
83
+ expected_src = uchi_titles_path(scope: {
84
+ model: "Book",
85
+ id: @book.id,
86
+ field: "titles",
87
+ inverse_of: "book"
88
+ })
89
+ assert_equal expected_src, src
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,73 @@
1
+ require "test_helper"
2
+
3
+ module Uchi
4
+ class ScopedRepositoryControllerTest < ActionDispatch::IntegrationTest
5
+ setup do
6
+ @book = Book.create!(original_title: "The Hobbit")
7
+ @dk_title = Title.create!(book: @book, locale: "da-DK", title: "Hobbitten")
8
+ @de_title = Title.create!(locale: "de-DE", title: "Der Hobbit", book: @book)
9
+
10
+ @scope = {model: "Book", id: @book.id, field: "titles"}
11
+ end
12
+
13
+ test "GET edit responds successfully" do
14
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
15
+ assert_response :success
16
+ end
17
+
18
+ test "GET edit renders a form for updating the model" do
19
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
20
+ assert_select "form[action=?][method='post']", uchi_title_path(id: @dk_title.id)
21
+ end
22
+
23
+ test "GET edit links back to the scoped model" do
24
+ get edit_uchi_title_url(id: @dk_title.id, scope: @scope)
25
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
26
+ end
27
+
28
+ test "GET new responds successfully" do
29
+ get new_uchi_title_url(scope: @scope)
30
+ assert_response :success
31
+ end
32
+
33
+ test "GET new renders a form to create a new model" do
34
+ get new_uchi_title_url(scope: @scope)
35
+ assert_select "form[action=?][method='post']", uchi_titles_path
36
+ end
37
+
38
+ test "GET new links back to the scoped model" do
39
+ get new_uchi_title_url(scope: @scope)
40
+ assert_select "a[href=?]", uchi_book_path(id: @book.id), text: "Cancel"
41
+ end
42
+
43
+ test "GET index responds successfully" do
44
+ get uchi_titles_url(scope: @scope)
45
+ assert_response :success
46
+ end
47
+
48
+ test "GET index renders the index view" do
49
+ get uchi_titles_url(scope: @scope)
50
+ assert_template :index
51
+ end
52
+
53
+ test "GET index includes a turbo-frame" do
54
+ get uchi_titles_url(scope: @scope)
55
+ assert_select "turbo-frame"
56
+ end
57
+
58
+ test "GET index lists only the records associated with the parent record" do
59
+ other_book = Book.create!(original_title: "1984")
60
+ Title.create!(locale: "de-DE", title: "1984", book: other_book)
61
+
62
+ get uchi_titles_url(scope: @scope.merge(inverse_of: "book"))
63
+
64
+ assert_equal assigns(:records), [@dk_title, @de_title]
65
+ end
66
+
67
+ test "GET index does not include the field for the parent record" do
68
+ get uchi_titles_url(scope: @scope.merge(inverse_of: "book"))
69
+
70
+ assert_not assigns(:columns).find { |column| column.name == :book }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
3
+ allow_browser versions: :modern
4
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class AuthorsController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Author
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class BooksController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Book
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Uchi
2
+ class TitlesController < Uchi::RepositoryController
3
+ def repository_class
4
+ Uchi::Repositories::Title
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: "from@example.com"
3
+ layout "mailer"
4
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ primary_abstract_class
3
+ end
@@ -0,0 +1,3 @@
1
+ class Author < ApplicationRecord
2
+ validates :name, presence: true
3
+ end
@@ -0,0 +1,3 @@
1
+ class Book < ApplicationRecord
2
+ has_many :titles
3
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ class Title < ApplicationRecord
2
+ belongs_to :book
3
+ end
@@ -0,0 +1,20 @@
1
+ module Uchi
2
+ module Repositories
3
+ class Author < Repository
4
+ def fields
5
+ [
6
+ Field::Number.new(:id, on: [:index, :show]),
7
+ Field::String.new(:name),
8
+ Field::Date.new(:born_on),
9
+ Field::String.new(:biography, on: [:edit, :new, :show])
10
+ ]
11
+ end
12
+
13
+ def title(model)
14
+ return nil unless model
15
+
16
+ model.name
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Uchi
2
+ module Repositories
3
+ class Book < Repository
4
+ def fields
5
+ [
6
+ Field::HasMany.new(:titles),
7
+ Field::String.new(:original_title)
8
+ ]
9
+ end
10
+
11
+ def title(model)
12
+ model.original_title
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Uchi
2
+ module Repositories
3
+ class Title < Repository
4
+ def fields
5
+ [
6
+ Field::BelongsTo.new(:book),
7
+ Field::String.new(:locale),
8
+ Field::String.new(:title)
9
+ ]
10
+ end
11
+
12
+ def title(model)
13
+ model.title
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= content_for(:title) || "Dummy" %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <meta name="mobile-web-app-capable" content="yes">
8
+ <%= csrf_meta_tags %>
9
+ <%= csp_meta_tag %>
10
+
11
+ <%= yield :head %>
12
+
13
+ <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
14
+ <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
15
+
16
+ <link rel="icon" href="/icon.png" type="image/png">
17
+ <link rel="icon" href="/icon.svg" type="image/svg+xml">
18
+ <link rel="apple-touch-icon" href="/icon.png">
19
+
20
+ <%# Includes all stylesheet files in app/assets/stylesheets %>
21
+ <%= stylesheet_link_tag :app %>
22
+ </head>
23
+
24
+ <body>
25
+ <%= yield %>
26
+ </body>
27
+ </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "Dummy",
3
+ "icons": [
4
+ {
5
+ "src": "/icon.png",
6
+ "type": "image/png",
7
+ "sizes": "512x512"
8
+ },
9
+ {
10
+ "src": "/icon.png",
11
+ "type": "image/png",
12
+ "sizes": "512x512",
13
+ "purpose": "maskable"
14
+ }
15
+ ],
16
+ "start_url": "/",
17
+ "display": "standalone",
18
+ "scope": "/",
19
+ "description": "Dummy.",
20
+ "theme_color": "red",
21
+ "background_color": "red"
22
+ }