├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── assets
└── templates
│ └── default.txt
├── bin
├── console
├── setup
└── test
│ ├── server-puma
│ ├── todos-api
│ ├── todos-hotwire
│ └── todos-scaffold
├── examples
├── hello-world.rb
├── server-falcon-app.rb
├── server-puma-app.rb
├── stimulus-app.rb
├── todos-api.rb
├── todos-hotwire.rb
└── todos-scaffold.rb
├── exe
└── unirails
├── features
├── server-puma
│ ├── Dockerfile
│ ├── Gemfile
│ ├── app.rb
│ ├── app_test.rb
│ └── docker-compose.yml
├── todos-api
│ ├── .DS_Store
│ ├── Dockerfile
│ ├── Gemfile
│ ├── app.rb
│ ├── app_test.rb
│ └── docker-compose.yml
├── todos-hotwire
│ ├── Dockerfile
│ ├── Gemfile
│ ├── app.rb
│ ├── app_test.rb
│ └── docker-compose.yml
└── todos-scaffold
│ ├── .DS_Store
│ ├── Dockerfile
│ ├── Gemfile
│ ├── app.rb
│ ├── app_test.rb
│ └── docker-compose.yml
├── lib
├── uni_rails.rb
└── uni_rails
│ ├── app.rb
│ ├── app
│ ├── css.rb
│ ├── javascript.rb
│ └── views.rb
│ ├── helpers.rb
│ ├── helpers
│ ├── css_helper.rb
│ └── javascript_helper.rb
│ └── version.rb
├── sig
└── uni_rails.rbs
├── test
├── test_helper.rb
└── test_uni_rails.rb
└── uni_rails.gemspec
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7 |
8 | name: Ruby
9 |
10 | on:
11 | push:
12 | branches: [ main ]
13 | pull_request:
14 | branches: [ main ]
15 |
16 | jobs:
17 | unit-tests:
18 | name: Unit tests
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Set up Ruby
23 | uses: ruby/setup-ruby@v1
24 | with:
25 | ruby-version: 3.3
26 | bundler-cache: true
27 | - run: bundle install
28 | - run: bundle exec rake
29 |
30 | app-tests:
31 | name: ${{ matrix.repo }} feature specs
32 | runs-on: ubuntu-latest
33 | strategy:
34 | matrix:
35 | repo:
36 | - todos-scaffold
37 | - todos-hotwire
38 | - todos-api
39 | - server-puma
40 | steps:
41 | - uses: actions/checkout@v4
42 | - name: Set up Ruby
43 | uses: ruby/setup-ruby@v1
44 | with:
45 | ruby-version: 3.3
46 | bundler-cache: true
47 | - run: bin/test/${{ matrix.repo }}
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | examples/*.sqlite*
10 | **/*.sqlite*
11 | log/*
12 | **/uni_rails.gem
13 | .byebug_history
14 | .ruby-version
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or
22 | advances of any kind
23 | * Trolling, insulting or derogatory comments, and personal or political attacks
24 | * Public or private harassment
25 | * Publishing others' private information, such as a physical or email
26 | address, without their explicit permission
27 | * Other conduct which could reasonably be considered inappropriate in a
28 | professional setting
29 |
30 | ## Enforcement Responsibilities
31 |
32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33 |
34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35 |
36 | ## Scope
37 |
38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39 |
40 | ## Enforcement
41 |
42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at barret.alx@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43 |
44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45 |
46 | ## Enforcement Guidelines
47 |
48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49 |
50 | ### 1. Correction
51 |
52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53 |
54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55 |
56 | ### 2. Warning
57 |
58 | **Community Impact**: A violation through a single incident or series of actions.
59 |
60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61 |
62 | ### 3. Temporary Ban
63 |
64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65 |
66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67 |
68 | ### 4. Permanent Ban
69 |
70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71 |
72 | **Consequence**: A permanent ban from any sort of public interaction within the community.
73 |
74 | ## Attribution
75 |
76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78 |
79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80 |
81 | [homepage]: https://www.contributor-covenant.org
82 |
83 | For answers to common questions about this code of conduct, see the FAQ at
84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
85 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in uni_rails.gemspec
6 | gemspec
7 |
8 | gem "rake", "~> 13.0"
9 | gem "minitest", "~> 5.0"
10 | gem "rack-test"
11 | gem "debug"
12 | gem "sqlite3"
13 | gem "puma"
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | uni_rails (0.5.0)
5 | rackup (~> 2.1)
6 | rails (~> 7.2.0)
7 | turbo-rails (~> 2.0)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | actioncable (7.2.0)
13 | actionpack (= 7.2.0)
14 | activesupport (= 7.2.0)
15 | nio4r (~> 2.0)
16 | websocket-driver (>= 0.6.1)
17 | zeitwerk (~> 2.6)
18 | actionmailbox (7.2.0)
19 | actionpack (= 7.2.0)
20 | activejob (= 7.2.0)
21 | activerecord (= 7.2.0)
22 | activestorage (= 7.2.0)
23 | activesupport (= 7.2.0)
24 | mail (>= 2.8.0)
25 | actionmailer (7.2.0)
26 | actionpack (= 7.2.0)
27 | actionview (= 7.2.0)
28 | activejob (= 7.2.0)
29 | activesupport (= 7.2.0)
30 | mail (>= 2.8.0)
31 | rails-dom-testing (~> 2.2)
32 | actionpack (7.2.0)
33 | actionview (= 7.2.0)
34 | activesupport (= 7.2.0)
35 | nokogiri (>= 1.8.5)
36 | racc
37 | rack (>= 2.2.4, < 3.2)
38 | rack-session (>= 1.0.1)
39 | rack-test (>= 0.6.3)
40 | rails-dom-testing (~> 2.2)
41 | rails-html-sanitizer (~> 1.6)
42 | useragent (~> 0.16)
43 | actiontext (7.2.0)
44 | actionpack (= 7.2.0)
45 | activerecord (= 7.2.0)
46 | activestorage (= 7.2.0)
47 | activesupport (= 7.2.0)
48 | globalid (>= 0.6.0)
49 | nokogiri (>= 1.8.5)
50 | actionview (7.2.0)
51 | activesupport (= 7.2.0)
52 | builder (~> 3.1)
53 | erubi (~> 1.11)
54 | rails-dom-testing (~> 2.2)
55 | rails-html-sanitizer (~> 1.6)
56 | activejob (7.2.0)
57 | activesupport (= 7.2.0)
58 | globalid (>= 0.3.6)
59 | activemodel (7.2.0)
60 | activesupport (= 7.2.0)
61 | activerecord (7.2.0)
62 | activemodel (= 7.2.0)
63 | activesupport (= 7.2.0)
64 | timeout (>= 0.4.0)
65 | activestorage (7.2.0)
66 | actionpack (= 7.2.0)
67 | activejob (= 7.2.0)
68 | activerecord (= 7.2.0)
69 | activesupport (= 7.2.0)
70 | marcel (~> 1.0)
71 | activesupport (7.2.0)
72 | base64
73 | bigdecimal
74 | concurrent-ruby (~> 1.0, >= 1.3.1)
75 | connection_pool (>= 2.2.5)
76 | drb
77 | i18n (>= 1.6, < 2)
78 | logger (>= 1.4.2)
79 | minitest (>= 5.1)
80 | securerandom (>= 0.3)
81 | tzinfo (~> 2.0, >= 2.0.5)
82 | base64 (0.2.0)
83 | bigdecimal (3.1.7)
84 | builder (3.2.4)
85 | concurrent-ruby (1.3.4)
86 | connection_pool (2.4.1)
87 | crass (1.0.6)
88 | date (3.3.4)
89 | debug (1.9.2)
90 | irb (~> 1.10)
91 | reline (>= 0.3.8)
92 | drb (2.2.1)
93 | erubi (1.12.0)
94 | globalid (1.2.1)
95 | activesupport (>= 6.1)
96 | i18n (1.14.4)
97 | concurrent-ruby (~> 1.0)
98 | io-console (0.7.2)
99 | irb (1.14.0)
100 | rdoc (>= 4.0.0)
101 | reline (>= 0.4.2)
102 | logger (1.6.0)
103 | loofah (2.22.0)
104 | crass (~> 1.0.2)
105 | nokogiri (>= 1.12.0)
106 | mail (2.8.1)
107 | mini_mime (>= 0.1.1)
108 | net-imap
109 | net-pop
110 | net-smtp
111 | marcel (1.0.4)
112 | mini_mime (1.1.5)
113 | minitest (5.22.3)
114 | net-imap (0.4.10)
115 | date
116 | net-protocol
117 | net-pop (0.1.2)
118 | net-protocol
119 | net-protocol (0.2.2)
120 | timeout
121 | net-smtp (0.5.0)
122 | net-protocol
123 | nio4r (2.7.1)
124 | nokogiri (1.16.3-arm64-darwin)
125 | racc (~> 1.4)
126 | nokogiri (1.16.3-x86_64-darwin)
127 | racc (~> 1.4)
128 | nokogiri (1.16.3-x86_64-linux)
129 | racc (~> 1.4)
130 | psych (5.1.2)
131 | stringio
132 | puma (6.4.2)
133 | nio4r (~> 2.0)
134 | racc (1.7.3)
135 | rack (3.0.10)
136 | rack-session (2.0.0)
137 | rack (>= 3.0.0)
138 | rack-test (2.1.0)
139 | rack (>= 1.3)
140 | rackup (2.1.0)
141 | rack (>= 3)
142 | webrick (~> 1.8)
143 | rails (7.2.0)
144 | actioncable (= 7.2.0)
145 | actionmailbox (= 7.2.0)
146 | actionmailer (= 7.2.0)
147 | actionpack (= 7.2.0)
148 | actiontext (= 7.2.0)
149 | actionview (= 7.2.0)
150 | activejob (= 7.2.0)
151 | activemodel (= 7.2.0)
152 | activerecord (= 7.2.0)
153 | activestorage (= 7.2.0)
154 | activesupport (= 7.2.0)
155 | bundler (>= 1.15.0)
156 | railties (= 7.2.0)
157 | rails-dom-testing (2.2.0)
158 | activesupport (>= 5.0.0)
159 | minitest
160 | nokogiri (>= 1.6)
161 | rails-html-sanitizer (1.6.0)
162 | loofah (~> 2.21)
163 | nokogiri (~> 1.14)
164 | railties (7.2.0)
165 | actionpack (= 7.2.0)
166 | activesupport (= 7.2.0)
167 | irb (~> 1.13)
168 | rackup (>= 1.0.0)
169 | rake (>= 12.2)
170 | thor (~> 1.0, >= 1.2.2)
171 | zeitwerk (~> 2.6)
172 | rake (13.1.0)
173 | rdoc (6.6.3.1)
174 | psych (>= 4.0.0)
175 | reline (0.5.0)
176 | io-console (~> 0.5)
177 | securerandom (0.3.1)
178 | sqlite3 (1.7.3-arm64-darwin)
179 | sqlite3 (1.7.3-x86_64-darwin)
180 | sqlite3 (1.7.3-x86_64-linux)
181 | stringio (3.1.0)
182 | thor (1.3.1)
183 | timeout (0.4.1)
184 | turbo-rails (2.0.5)
185 | actionpack (>= 6.0.0)
186 | activejob (>= 6.0.0)
187 | railties (>= 6.0.0)
188 | tzinfo (2.0.6)
189 | concurrent-ruby (~> 1.0)
190 | useragent (0.16.10)
191 | webrick (1.8.1)
192 | websocket-driver (0.7.6)
193 | websocket-extensions (>= 0.1.0)
194 | websocket-extensions (0.1.5)
195 | zeitwerk (2.6.13)
196 |
197 | PLATFORMS
198 | arm64-darwin-23
199 | x86_64-darwin-21
200 | x86_64-darwin-23
201 | x86_64-linux
202 |
203 | DEPENDENCIES
204 | debug
205 | minitest (~> 5.0)
206 | puma
207 | rack-test
208 | rake (~> 13.0)
209 | sqlite3
210 | uni_rails!
211 |
212 | BUNDLED WITH
213 | 2.4.22
214 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Alexandre Barret
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UniRails
2 |
3 | UniRails makes it easy to build Ruby on Rails apps all within one Ruby file. It's perfect for small personal projects or teaching, as everything is one scroll away.
4 |
5 | We aim to help educators and writers create clear examples for articles or books. Rails requires a full folder structure to show the basics, which can be a hassle. UniRails cuts through that by letting you spin up a complete Rails app from just one Ruby file — everything you need in one place.
6 |
7 | Check out our [examples](/examples) to understand how the library creates Rails apps from a single file.
8 |
9 | ## Installation & Usage
10 |
11 | See some examples of how the UniRails library can be used. Running an example is a easy as `ruby filename.rb`
12 |
13 | - [Hello world](/examples/hello-world.rb)
14 | - [Todos app (JSON API)](/examples/todos-api.rb)
15 | - [Todos app (Rails scaffold)](/examples/todos-scaffold.rb) based off `bin/rails g scaffold todo name completed_at:datetime`
16 | - [Todos app (Hotwire)](/examples/todos-hotwire.rb) based off online article: [turbo-rails-101-todo-list](https://www.colby.so/posts/turbo-rails-101-todo-list) by David Colby
17 | - [App using StimulusJS](/examples/stimulus-app.rb)
18 | - [App using Puma server](/examples/server-puma-app.rb)
19 | - [App using Falcon server](/examples/server-falcon-app.rb)
20 |
21 | ## Development
22 |
23 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24 |
25 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26 |
27 | ## Contributing
28 |
29 | Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/uni_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/uni_rails/blob/main/CODE_OF_CONDUCT.md).
30 |
31 | ### Roadmap
32 |
33 | We would like to support all railties and engines. Please help us support more of them
34 |
35 | - [X] action_controller
36 | - [X] active_record
37 | - [X] active_model/railtie
38 | - [ ] active_job/railtie
39 | - [ ] active_storage/engine
40 | - [ ] action_mailer/railtie
41 | - [ ] action_mailbox/engine
42 | - [ ] action_text/engine
43 | - [ ] action_view/railtie
44 | - [ ] action_cable/engine
45 |
46 |
47 | ## License
48 |
49 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
50 |
51 | ## Code of Conduct
52 |
53 | Everyone interacting in the UniRails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/uni_rails/blob/main/CODE_OF_CONDUCT.md).
54 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require "rake/testtask"
5 |
6 | Rake::TestTask.new(:test) do |t|
7 | t.libs << "test"
8 | t.libs << "lib"
9 | t.test_files = FileList["test/**/test_*.rb"]
10 | end
11 |
12 | task default: :test
13 |
--------------------------------------------------------------------------------
/assets/templates/default.txt:
--------------------------------------------------------------------------------
1 | ENV["SECRET_KEY_BASE"] = "1212312313"
2 | ENV["DATABASE_URL"] = "sqlite3:///#{__dir__}/database.sqlite"
3 |
4 | require "bundler/inline"
5 |
6 | gemfile do
7 | source "https://www.rubygems.org"
8 | gem "uni_rails", "~> 0.4.0"
9 | gem "sqlite3", "~> 1.7"
10 | gem "byebug"
11 | end
12 |
13 | require "uni_rails"
14 | require "sqlite3"
15 | require "byebug"
16 |
17 | # ==== ROUTES ====
18 | UniRails.routes do
19 | root "resource_names#new"
20 | resources :resource_names
21 | end
22 |
23 | # ==== DB SCHEMA ====
24 | ActiveRecord::Base.establish_connection
25 | ActiveRecord::Schema.define do
26 | create_table :resources, force: :cascade do |t|
27 | t.string :title
28 | end
29 | end
30 |
31 | # ==== MODELS ====
32 | class Resource < ActiveRecord::Base
33 | end
34 |
35 | # ==== SEEDS ====
36 | # Create your existing records here
37 |
38 | # ==== CONTROLLERS ====
39 | class ResourceNamesController < ActionController::Base
40 | layout 'application'
41 |
42 | def new
43 | end
44 |
45 | def create
46 | end
47 | end
48 |
49 | # ==== IMPORT MAPS ====
50 | UniRails.import_maps(
51 | 'stimulus' => 'https://unpkg.com/@hotwired/stimulus/dist/stimulus.js'
52 | )
53 |
54 |
55 | # ==== JAVASCRTIP ====
56 | UniRails.javascript <<~JAVASCRIPT
57 | import { Application, Controller } from "stimulus"
58 | window.Stimulus = Application.start()
59 |
60 | Stimulus.register("hello", class extends Controller {
61 | connect() {
62 | console.log("hello world")
63 | }
64 | })
65 |
66 | JAVASCRIPT
67 |
68 | # ==== CSS ====
69 | UniRails.css <<~CSS
70 | html { background-color:#EEE; }
71 | body { width:500px; height:700px; margin:auto;
72 | background-color:white; padding:1rem;
73 | }
74 | form {
75 | label { display: block; }
76 | input[type="submit"] { display: block; margin-top:1rem; }
77 | .field_with_errors { color:red; display:inline; }
78 | }
79 | CSS
80 |
81 | # ==== VIEWS ====
82 | UniRails.register_view "resource_names/new.html.erb", <<~HTML
83 | HTML
84 |
85 | UniRails.register_view "resource_names/show.html.erb", <<~HTML
86 | HTML
87 |
88 | UniRails.run(Port: 3000)
89 | # UniRails.run(Host: '0.0.0.0', Port: 3000) # when run in a docker container
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "uni_rails"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | require "irb"
11 | IRB.start(__FILE__)
12 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/bin/test/server-puma:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle install
4 | bundle exec rake build
5 | ls -t pkg | head -n1 | xargs -I {} mv pkg/{} features/server-puma/uni_rails.gem
6 | docker compose -f features/server-puma/docker-compose.yml up --build --exit-code-from test
--------------------------------------------------------------------------------
/bin/test/todos-api:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle install
4 | bundle exec rake build
5 | ls -t pkg | head -n1 | xargs -I {} mv pkg/{} features/todos-api/uni_rails.gem
6 | docker compose -f features/todos-api/docker-compose.yml up --build --exit-code-from test
--------------------------------------------------------------------------------
/bin/test/todos-hotwire:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle install
4 | bundle exec rake build
5 | ls -t pkg | head -n1 | xargs -I {} mv pkg/{} features/todos-hotwire/uni_rails.gem
6 | docker compose -f features/todos-hotwire/docker-compose.yml up --build --exit-code-from test
--------------------------------------------------------------------------------
/bin/test/todos-scaffold:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle install
4 | bundle exec rake build
5 | ls -t pkg | head -n1 | xargs -I {} mv pkg/{} features/todos-scaffold/uni_rails.gem
6 | docker compose -f features/todos-scaffold/docker-compose.yml up --build --exit-code-from test
--------------------------------------------------------------------------------
/examples/hello-world.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 |
4 | require "bundler/inline"
5 |
6 | gemfile(true) do
7 | source "https://rubygems.org"
8 | gem 'uni_rails', '~> 0.5.0'
9 | end
10 |
11 | require 'uni_rails'
12 |
13 | UniRails.routes do
14 | root 'pages#index'
15 | end
16 |
17 | class PagesController < ActionController::Base
18 | def index
19 | render plain: 'Hello world'
20 | end
21 | end
22 |
23 | UniRails.run(Port: 3000)
24 |
--------------------------------------------------------------------------------
/examples/server-falcon-app.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 |
4 | require "bundler/inline"
5 |
6 | gemfile(true) do
7 | source "https://rubygems.org"
8 |
9 | gem 'uni_rails', '~> 0.5.0'
10 | gem 'falcon'
11 | end
12 |
13 | require 'uni_rails'
14 | require 'rackup/handler/falcon'
15 |
16 | UniRails.rackup_handler = Rackup::Handler::Falcon
17 |
18 | UniRails.routes do
19 | root 'pages#index'
20 | end
21 |
22 | class PagesController < ActionController::Base
23 | def index
24 | render plain: 'Application served by Falcon'
25 | end
26 | end
27 |
28 | UniRails.run(Port: 3000)
29 |
--------------------------------------------------------------------------------
/examples/server-puma-app.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 |
4 | require "bundler/inline"
5 |
6 | gemfile(true) do
7 | source "https://rubygems.org"
8 |
9 | gem 'uni_rails', '~> 0.5.0'
10 | gem 'puma'
11 | end
12 |
13 | require 'uni_rails'
14 | require 'rack/handler/puma'
15 |
16 | UniRails.rackup_handler = Rack::Handler::Puma
17 |
18 | UniRails.routes do
19 | root 'pages#index'
20 | end
21 |
22 | class PagesController < ActionController::Base
23 | def index
24 | render plain: 'Application served by Puma'
25 | end
26 | end
27 |
28 | UniRails.run(Port: 3000)
29 |
--------------------------------------------------------------------------------
/examples/stimulus-app.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 |
4 | require "bundler/inline"
5 |
6 | gemfile(true) do
7 | source "https://rubygems.org"
8 | gem 'uni_rails', '~> 0.5.0'
9 | end
10 |
11 | require 'uni_rails'
12 |
13 | UniRails.routes do
14 | root 'pages#index'
15 | end
16 |
17 | UniRails.import_maps(
18 | 'stimulus' => 'https://unpkg.com/@hotwired/stimulus/dist/stimulus.js'
19 | )
20 |
21 | UniRails.javascript <<~JAVASCRIPT
22 | import { Application, Controller } from "stimulus"
23 | window.Stimulus = Application.start()
24 |
25 | Stimulus.register("hello", class extends Controller {
26 | connect() {
27 | console.log("hello world")
28 | }
29 | })
30 | JAVASCRIPT
31 |
32 | class PagesController < ActionController::Base
33 | layout 'application'
34 | def index;end
35 | end
36 |
37 | UniRails.register_view "pages/index.html.erb", <<~HTML
38 |
Check out the dev console to see "hello world"
39 | HTML
40 |
41 | UniRails.run(Port: 3000)
42 |
--------------------------------------------------------------------------------
/examples/todos-api.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 | ENV['DATABASE_URL'] = "sqlite3:///#{Dir.pwd}/todos-api.sqlite"
4 |
5 | require "bundler/inline"
6 |
7 | gemfile(true) do
8 | source "https://rubygems.org"
9 |
10 | gem 'uni_rails', '~> 0.5.0'
11 | gem 'sqlite3', '~> 1.7'
12 | end
13 |
14 | require 'uni_rails'
15 | require 'sqlite3'
16 |
17 | ActiveRecord::Base.establish_connection
18 | ActiveRecord::Schema.define do
19 | create_table :todos, force: :cascade do |t|
20 | t.string :name
21 | t.datetime :completed_at
22 | t.timestamps
23 | end
24 | end
25 |
26 | UniRails.routes do
27 | resources :todos do
28 | put :complete, on: :member
29 | end
30 | end
31 |
32 | # MODELS
33 |
34 | class Todo < ActiveRecord::Base
35 | validates :name, presence: true
36 |
37 | def complete(at: Time.zone.now)
38 | update(completed_at: at)
39 | end
40 | end
41 |
42 | # CONTROLLERS
43 |
44 | class ApplicationController < ActionController::Base
45 | protect_from_forgery with: :null_session
46 | end
47 |
48 | class TodosController < ApplicationController
49 | before_action :set_todo, only: [:show, :update, :destroy, :complete]
50 |
51 | def index
52 | @todos = Todo.all
53 | render json: @todos
54 | end
55 |
56 | def show
57 | render json: @todo
58 | end
59 |
60 | def create
61 | @todo = Todo.new(todo_params)
62 | if @todo.save
63 | render json: @todo, status: :created, location: @todo
64 | else
65 | render json: @todo.errors, status: :unprocessable_entity
66 | end
67 | end
68 |
69 | def update
70 | if @todo.update(todo_params)
71 | render json: @todo
72 | else
73 | render json: @todo.errors, status: :unprocessable_entity
74 | end
75 | end
76 |
77 | def complete
78 | if @todo.complete
79 | render json: @todo
80 | else
81 | render json: @todo.errors, status: :unprocessable_entity
82 | end
83 | end
84 |
85 | def destroy
86 | @todo.destroy
87 | head :no_content
88 | end
89 |
90 | private
91 |
92 | def set_todo
93 | @todo = Todo.find(params[:id])
94 | end
95 |
96 | def todo_params
97 | params.require(:todo).permit(:name, :status)
98 | end
99 | end
100 |
101 | UniRails.run(Port: 3000)
102 |
--------------------------------------------------------------------------------
/examples/todos-hotwire.rb:
--------------------------------------------------------------------------------
1 | # Based on the Hotwire tutorial: https://www.colby.so/posts/turbo-rails-101-todo-list
2 |
3 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
4 | ENV['DATABASE_URL'] = "sqlite3:///#{Dir.pwd}/todos-hotwire.sqlite"
5 |
6 | require "bundler/inline"
7 |
8 | gemfile(true) do
9 | source "https://rubygems.org"
10 |
11 | gem 'uni_rails', '~> 0.5.0'
12 | gem 'sqlite3', '~> 1.7'
13 | gem 'debug'
14 | end
15 |
16 | require 'uni_rails'
17 | require 'sqlite3'
18 |
19 | ActiveRecord::Base.establish_connection
20 | ActiveRecord::Schema.define do
21 | create_table :todos, force: :cascade do |t|
22 | t.string :name
23 | t.integer :status, default: 0
24 | t.timestamps
25 | end
26 | end
27 |
28 | UniRails.enable_turbo_rails!
29 |
30 | UniRails.routes do
31 | root 'todos#index'
32 | resources :todos do
33 | patch :change_status, on: :member
34 | end
35 | end
36 |
37 | # MODELS
38 |
39 | class Todo < ActiveRecord::Base
40 | enum status: {
41 | incomplete: 0,
42 | complete: 1
43 | }
44 |
45 | validates :name, presence: true
46 | end
47 |
48 |
49 | # CONTROLLERS
50 |
51 | class TodosController < ActionController::Base
52 | layout 'application'
53 |
54 | before_action :set_todo, only: [:edit, :update, :destroy, :change_status]
55 |
56 | def index
57 | @todos = Todo.where(status: params[:status].presence || 'incomplete')
58 | end
59 |
60 | def new
61 | @todo = Todo.new
62 | end
63 |
64 | def edit
65 | @todo = Todo.find(params[:id])
66 | end
67 |
68 | def create
69 | @todo = Todo.new(todo_params)
70 |
71 | respond_to do |format|
72 | if @todo.save
73 | format.turbo_stream
74 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully created." }
75 | else
76 | format.turbo_stream { render turbo_stream: turbo_stream.replace("#{helpers.dom_id(@todo)}_form", partial: "form", locals: { todo: @todo }) }
77 | format.html { render :new, status: :unprocessable_entity }
78 | end
79 | end
80 | end
81 |
82 | def update
83 | respond_to do |format|
84 | if @todo.update(todo_params)
85 | format.turbo_stream
86 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully updated." }
87 | format.json { render :show, status: :ok, location: @todo }
88 | else
89 | format.turbo_stream { render turbo_stream: turbo_stream.replace("#{helpers.dom_id(@todo)}_form", partial: "form", locals: { todo: @todo }) }
90 | format.html { render :edit, status: :unprocessable_entity }
91 | format.json { render json: @todo.errors, status: :unprocessable_entity }
92 | end
93 | end
94 | end
95 |
96 | def destroy
97 | @todo.destroy
98 |
99 | respond_to do |format|
100 | format.turbo_stream { render turbo_stream: turbo_stream.remove("#{helpers.dom_id(@todo)}_container") }
101 | format.html { redirect_to todos_url, notice: "Todo was successfully destroyed." }
102 | format.json { head :no_content }
103 | end
104 | end
105 |
106 | def change_status
107 | @todo.update(status: todo_params[:status])
108 | respond_to do |format|
109 | format.turbo_stream { render turbo_stream: turbo_stream.remove("#{helpers.dom_id(@todo)}_container") }
110 | format.html { redirect_to todos_path, notice: "Updated todo status." }
111 | end
112 | end
113 |
114 | private
115 |
116 | def set_todo
117 | @todo = Todo.find(params[:id])
118 | end
119 |
120 | def todo_params
121 | params.require(:todo).permit(:name, :status)
122 | end
123 | end
124 |
125 | # VIEWS
126 |
127 | UniRails.javascript <<~JAVASCRIPT
128 | import * as Turbo from "turbo"
129 | JAVASCRIPT
130 |
131 | UniRails.register_view "layouts/application.html.erb", <<~'HTML'
132 |
133 |
134 |
135 | Template
136 |
137 | <%= csrf_meta_tags %>
138 | <%= csp_meta_tag %>
139 |
140 | <%= uni_rails_css_stylesheet %>
141 | <%= uni_rails_import_map_tag %>
142 | <%= uni_rails_javascript_script %>
143 |
144 |
145 |
146 | <%= yield %>
147 |
148 |
149 | HTML
150 |
151 | UniRails.register_view "todos/index.html.erb", <<~'HTML'
152 |
153 |
154 | Your todos
155 |
156 | <%= turbo_frame_tag "todos-container", class: "block max-w-2xl w-full bg-gray-100 py-8 px-4 border border-gray-200 rounded shadow-sm" do %>
157 |
158 |
159 | -
160 | <%= link_to "Incomplete",
161 | todos_path(status: "incomplete"),
162 | class: "inline-block py-4 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300"
163 | %>
164 |
-
165 | <%= link_to "Complete",
166 | todos_path(status: "complete"),
167 | class: "inline-block py-4 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300"
168 | %>
169 |
170 |
171 |
172 | <% unless params[:status] == "complete" %>
173 |
174 | <%= render "form", todo: Todo.new %>
175 |
176 | <% end %>
177 |
178 | <%= render @todos %>
179 |
180 | <% end %>
181 |
182 | HTML
183 |
184 | UniRails.register_view "todos/_form.html.erb", <<~'HTML'
185 | <%= form_with(model: todo, id: "#{dom_id(todo)}_form") do |form| %>
186 | <% if todo.errors.any? %>
187 |
188 |
<%= pluralize(todo.errors.count, "error") %> prohibited this todo from being saved:
189 |
190 |
191 | <% todo.errors.each do |error| %>
192 | - <%= error.full_message %>
193 | <% end %>
194 |
195 |
196 | <% end %>
197 |
198 | <%= form.label :name, class: "sr-only" %>
199 | <%= form.text_field :name, class: "block w-full rounded-none rounded-l-md sm:text-sm border-gray-300", placeholder: "Add a new todo..." %>
200 | <%= form.submit class: "-ml-px relative px-4 py-2 border border-blue-600 text-sm font-medium rounded-r-md text-white bg-blue-600 hover:bg-blue-700" %>
201 |
202 | <% end %>
203 | HTML
204 |
205 | UniRails.register_view "todos/_todo.html.erb", <<~'HTML'
206 | " class="py-2 px-4 border-b border-gray-300">
207 | <%= turbo_frame_tag dom_id(todo) do %>
208 |
209 | <%= link_to todo.name, edit_todo_path(todo), class: todo.complete? ? 'line-through' : '' %>
210 |
211 |
212 | <% if todo.complete? %>
213 | <%= button_to change_status_todo_path(todo, todo: { status: 'incomplete' }), class: "bg-green-600 px-4 py-2 rounded hover:bg-green-700", method: :patch do %>
214 |
Mark as incomplete
215 |
218 | <% end %>
219 | <% else %>
220 | <%= button_to change_status_todo_path(todo, todo: { status: 'complete' }), class: "bg-gray-400 px-4 py-2 rounded hover:bg-green-700", method: :patch do %>
221 |
Mark as complete
222 |
225 | <% end %>
226 | <% end %>
227 |
228 | <%= button_to todo_path(todo), class: "bg-red-600 px-4 py-2 rounded hover:bg-red-700", method: :delete do %>
229 |
Delete
230 |
233 | <% end %>
234 |
235 |
236 | <% end %>
237 |
238 | HTML
239 |
240 | UniRails.register_view "todos/edit.html.erb", <<~'HTML'
241 | <%= turbo_frame_tag dom_id(@todo) do %>
242 | <%= render "form", todo: @todo %>
243 | <% end %>
244 | HTML
245 |
246 | UniRails.register_view "todos/create.turbo_stream.erb", <<~'HTML'
247 | <%= turbo_stream.prepend "todos" do %>
248 | <%= render "todo", todo: @todo %>
249 | <% end %>
250 | <%= turbo_stream.replace "#{dom_id(Todo.new)}_form" do %>
251 | <%= render "form", todo: Todo.new %>
252 | <% end %>
253 | HTML
254 |
255 | UniRails.register_view "todos/update.turbo_stream.erb", <<~'HTML'
256 | <%= turbo_stream.replace "#{dom_id(@todo)}_container" do %>
257 | <%= render "todo", todo: @todo %>
258 | <% end %>
259 | HTML
260 |
261 | UniRails.run(Port: 3000)
262 |
--------------------------------------------------------------------------------
/examples/todos-scaffold.rb:
--------------------------------------------------------------------------------
1 |
2 | ENV['SECRET_KEY_BASE'] = 'my_secret_key_base'
3 | ENV['DATABASE_URL'] = "sqlite3:///#{Dir.pwd}/todos-scaffold.sqlite"
4 |
5 | require "bundler/inline"
6 |
7 | gemfile(true) do
8 | source "https://rubygems.org"
9 |
10 | gem 'uni_rails', '~> 0.5.0'
11 | gem 'sqlite3', '~> 1.7'
12 | gem 'jbuilder' # jbuilder allows the .jbuilder extension for views
13 | end
14 |
15 | require 'uni_rails'
16 | require 'sqlite3'
17 |
18 | ActiveRecord::Base.establish_connection
19 | ActiveRecord::Schema.define do
20 | create_table :todos, force: :cascade do |t|
21 | t.string :name
22 | t.datetime :completed_at
23 | t.timestamps
24 | end
25 | end
26 |
27 | UniRails.routes do
28 | root 'todos#index'
29 | resources :todos
30 | end
31 |
32 | # MODELS
33 |
34 | class Todo < ActiveRecord::Base
35 | validates :name, presence: true
36 |
37 | def complete(at: Time.zone.now)
38 | update(completed_at: at)
39 | end
40 | end
41 |
42 |
43 | # CONTROLLERS
44 | class ApplicationController < ActionController::Base
45 | end
46 |
47 | class TodosController < ApplicationController
48 | before_action :set_todo, only: %i[ show edit update destroy ]
49 |
50 | # GET /todos or /todos.json
51 | def index
52 | @todos = Todo.all
53 | end
54 |
55 | # GET /todos/1 or /todos/1.json
56 | def show
57 | end
58 |
59 | # GET /todos/new
60 | def new
61 | @todo = Todo.new
62 | end
63 |
64 | # GET /todos/1/edit
65 | def edit
66 | end
67 |
68 | # POST /todos or /todos.json
69 | def create
70 | @todo = Todo.new(todo_params)
71 |
72 | respond_to do |format|
73 | if @todo.save
74 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully created." }
75 | format.json { render :show, status: :created, location: @todo }
76 | else
77 | format.html { render :new, status: :unprocessable_entity }
78 | format.json { render json: @todo.errors, status: :unprocessable_entity }
79 | end
80 | end
81 | end
82 |
83 | # PATCH/PUT /todos/1 or /todos/1.json
84 | def update
85 | respond_to do |format|
86 | if @todo.update(todo_params)
87 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully updated." }
88 | format.json { render :show, status: :ok, location: @todo }
89 | else
90 | format.html { render :edit, status: :unprocessable_entity }
91 | format.json { render json: @todo.errors, status: :unprocessable_entity }
92 | end
93 | end
94 | end
95 |
96 | # DELETE /todos/1 or /todos/1.json
97 | def destroy
98 | @todo.destroy!
99 |
100 | respond_to do |format|
101 | format.html { redirect_to todos_url, notice: "Todo was successfully destroyed." }
102 | format.json { head :no_content }
103 | end
104 | end
105 |
106 | private
107 | # Use callbacks to share common setup or constraints between actions.
108 | def set_todo
109 | @todo = Todo.find(params[:id])
110 | end
111 |
112 | # Only allow a list of trusted parameters through.
113 | def todo_params
114 | params.require(:todo).permit(:name, :completed_at)
115 | end
116 | end
117 |
118 | # VIEWS - HTML
119 |
120 | UniRails.register_view "todos/_form.html.erb", <<~HTML
121 | <%= form_with(model: todo) do |form| %>
122 | <% if todo.errors.any? %>
123 |
124 |
<%= pluralize(todo.errors.count, "error") %> prohibited this todo from being saved:
125 |
126 |
127 | <% todo.errors.each do |error| %>
128 | - <%= error.full_message %>
129 | <% end %>
130 |
131 |
132 | <% end %>
133 |
134 |
135 | <%= form.label :name, style: "display: block" %>
136 | <%= form.text_field :name %>
137 |
138 |
139 |
140 | <%= form.label :completed_at, style: "display: block" %>
141 | <%= form.datetime_field :completed_at %>
142 |
143 |
144 |
145 | <%= form.submit %>
146 |
147 | <% end %>
148 | HTML
149 |
150 | UniRails.register_view "todos/_todo.html.erb", <<~HTML
151 |
152 |
153 | Name:
154 | <%= todo.name %>
155 |
156 |
157 |
158 | Completed at:
159 | <%= todo.completed_at %>
160 |
161 |
162 |
163 | HTML
164 |
165 | UniRails.register_view "todos/edit.html.erb", <<~HTML
166 | Editing todo
167 |
168 | <%= render "form", todo: @todo %>
169 |
170 |
171 |
172 |
173 | <%= link_to "Show this todo", @todo %> |
174 | <%= link_to "Back to todos", todos_path %>
175 |
176 | HTML
177 |
178 | UniRails.register_view "todos/index.html.erb", <<~HTML
179 | <%= notice %>
180 |
181 | Todos
182 |
183 |
184 | <% @todos.each do |todo| %>
185 | <%= render todo %>
186 |
187 | <%= link_to "Show this todo", todo %>
188 |
189 | <% end %>
190 |
191 |
192 | <%= link_to "New todo", new_todo_path %>
193 | HTML
194 |
195 | UniRails.register_view "todos/new.html.erb", <<~HTML
196 | New todo
197 |
198 | <%= render "form", todo: @todo %>
199 |
200 |
201 |
202 |
203 | <%= link_to "Back to todos", todos_path %>
204 |
205 | HTML
206 |
207 | UniRails.register_view "todos/show.html.erb", <<~HTML
208 | <%= notice %>
209 |
210 | <%= render @todo %>
211 |
212 |
213 | <%= link_to "Edit this todo", edit_todo_path(@todo) %> |
214 | <%= link_to "Back to todos", todos_path %>
215 |
216 | <%= button_to "Destroy this todo", @todo, method: :delete %>
217 |
218 | HTML
219 |
220 | # VIEWS - JSON
221 |
222 | UniRails.register_view "todos/_todo.json.jbuilder", <<~RUBY
223 | json.extract! todo, :id, :name, :completed_at, :created_at, :updated_at
224 | json.url todo_url(todo, format: :json)
225 | RUBY
226 |
227 | UniRails.register_view "todos/index.json.jbuilder", <<~RUBY
228 | json.array! @todos, partial: "todos/todo", as: :todo
229 | RUBY
230 |
231 | UniRails.register_view "todos/show.json.jbuilder", <<~RUBY
232 | json.partial! "todos/todo", todo: @todo
233 | RUBY
234 |
235 | UniRails.run(Port: 3000)
236 |
--------------------------------------------------------------------------------
/exe/unirails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # require "byebug"
4 | # byebug
5 |
6 | assets_path = File.expand_path('../assets', __dir__)
7 |
8 | options = {}
9 | case ARGV[0]
10 | when "new"
11 | filename = ARGV[1]
12 | File.open(filename, 'wb+') do |f|
13 | f.write File.read File.join(assets_path, 'templates', 'default.txt')
14 | end
15 | else
16 | puts "unknown command"
17 | puts "Usage: unirails new "
18 | end
19 |
--------------------------------------------------------------------------------
/features/server-puma/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.3.0-slim-bullseye
2 |
3 | ARG BUILD_PACKAGES="build-essential git curl libvips pkg-config sqlite3 libsqlite3-dev"
4 |
5 | # Install packages needed to build gems
6 | RUN apt-get update -qq && \
7 | apt-get install --no-install-recommends -y $BUILD_PACKAGES
8 |
9 | WORKDIR /usr/src/app
10 |
11 | ENV LANG C.UTF-8
12 | ENV BUNDLER_VERSION 2.1
13 | ENV GEM_HOME="/usr/local/bundle"
14 | ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
15 |
16 | COPY Gemfile uni_rails.gem ./
17 | RUN gem install uni_rails.gem
18 | RUN bundle install
19 |
20 | COPY . /usr/src/app
21 |
22 | CMD ["ruby", "app.rb"]
--------------------------------------------------------------------------------
/features/server-puma/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | gem 'uni_rails', path: '/usr/local/bundle'
6 | gem 'puma'
7 |
8 | # Test
9 | gem "capybara", "~> 3.40"
10 | gem "selenium-webdriver", "~> 4.19"
11 | gem "minitest"
12 | gem "debug"
13 |
--------------------------------------------------------------------------------
/features/server-puma/app.rb:
--------------------------------------------------------------------------------
1 | require 'uni_rails'
2 | require 'rack/handler/puma'
3 |
4 | UniRails.rackup_handler = Rack::Handler::Puma
5 |
6 | UniRails.routes do
7 | root 'pages#index'
8 | end
9 |
10 | class PagesController < ActionController::Base
11 | def index
12 | render plain: 'Application served by Puma'
13 | end
14 | end
15 |
16 | UniRails::App.configure do
17 | config.log_level = :error
18 | config.hosts << ENV['APP_HOST']
19 | end
20 |
21 | UniRails.run(Port: 3000, BindAddress: '0.0.0.0')
22 |
--------------------------------------------------------------------------------
/features/server-puma/app_test.rb:
--------------------------------------------------------------------------------
1 | require "capybara"
2 | require "capybara/dsl"
3 | require 'capybara/minitest'
4 | require "selenium-webdriver"
5 | require "minitest/autorun"
6 | require "debug"
7 |
8 | APP_HOST = ENV.fetch('APP_HOST')
9 |
10 | Capybara.register_driver :selenium_remote_chrome do |app|
11 | options = Selenium::WebDriver::Chrome::Options.new
12 | # Add any additional options you need
13 | options.add_argument('--headless') # Uncomment if you want to run headless mode
14 |
15 | Capybara::Selenium::Driver.new(app,
16 | browser: :remote,
17 | url: "http://selenium:4444/wd/hub",
18 | options: options
19 | )
20 | end
21 |
22 | Capybara.default_driver = :selenium_remote_chrome
23 | Capybara.app_host = APP_HOST
24 |
25 | class TestHTMLTodos < Minitest::Test
26 | include Capybara::DSL
27 | include Capybara::Minitest::Assertions
28 |
29 | def teardown
30 | Capybara.reset_sessions!
31 | Capybara.use_default_driver
32 | end
33 |
34 | def test_create_a_new_todo
35 | visit "/"
36 | assert_text 'Application served by Puma'
37 | end
38 | end
39 |
40 |
--------------------------------------------------------------------------------
/features/server-puma/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | uni_rails:
3 | build: .
4 | ports:
5 | - 127.0.0.1:3000:3000
6 | volumes:
7 | - .:/usr/src/app
8 | environment:
9 | - APP_HOST=uni_rails:3000
10 | - SECRET_KEY_BASE=whatever
11 | healthcheck:
12 | test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
13 | interval: 3s
14 | timeout: 5s
15 | retries: 3
16 | start_period: 5s
17 | command: ruby app.rb
18 | test:
19 | build: .
20 | volumes:
21 | - .:/usr/src/app
22 | environment:
23 | - APP_HOST=http://uni_rails:3000
24 | depends_on:
25 | uni_rails:
26 | condition: service_healthy
27 | selenium:
28 | condition: service_healthy
29 | command: ruby app_test.rb
30 | selenium:
31 | image: selenium/standalone-chrome:latest
32 | ports:
33 | - "4444:4444"
34 | - "7900:7900"
35 | healthcheck:
36 | test: ["CMD-SHELL", "curl -f http://localhost:4444/status || exit 1"]
37 | interval: 3s
38 | timeout: 5s
39 | retries: 3
40 | start_period: 5s
41 |
--------------------------------------------------------------------------------
/features/todos-api/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexB52/uni_rails/2fa0563f39433d7236edfdeefa02d301fb793425/features/todos-api/.DS_Store
--------------------------------------------------------------------------------
/features/todos-api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.3.0-slim-bullseye
2 |
3 | ARG BUILD_PACKAGES="build-essential git curl libvips pkg-config sqlite3 libsqlite3-dev"
4 |
5 | # Install packages needed to build gems
6 | RUN apt-get update -qq && \
7 | apt-get install --no-install-recommends -y $BUILD_PACKAGES
8 |
9 | WORKDIR /usr/src/app
10 |
11 | ENV LANG C.UTF-8
12 | ENV BUNDLER_VERSION 2.1
13 | ENV GEM_HOME="/usr/local/bundle"
14 | ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
15 |
16 | COPY Gemfile uni_rails.gem ./
17 | RUN gem install uni_rails.gem
18 | RUN bundle install
19 |
20 | COPY . /usr/src/app
21 |
22 | CMD ["ruby", "app.rb"]
--------------------------------------------------------------------------------
/features/todos-api/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | gem 'uni_rails', path: '/usr/local/bundle'
6 | gem "sqlite3", "~> 1.7"
7 |
8 | # Test
9 | gem "capybara", "~> 3.40"
10 | gem "selenium-webdriver", "~> 4.19"
11 | gem "minitest"
12 | gem "debug"
13 |
--------------------------------------------------------------------------------
/features/todos-api/app.rb:
--------------------------------------------------------------------------------
1 | require 'uni_rails'
2 | require 'debug'
3 | require 'sqlite3'
4 |
5 | ActiveRecord::Base.establish_connection
6 | ActiveRecord::Schema.define do
7 | create_table :todos, force: :cascade do |t|
8 | t.string :name
9 | t.datetime :completed_at
10 | t.timestamps
11 | end
12 | end
13 |
14 | UniRails.routes do
15 | resources :todos do
16 | put :complete, on: :member
17 | end
18 | end
19 |
20 | # MODELS
21 |
22 | class Todo < ActiveRecord::Base
23 | validates :name, presence: true
24 |
25 | def complete(at: Time.zone.now)
26 | update(completed_at: at)
27 | end
28 | end
29 |
30 | # CONTROLLERS
31 |
32 | class ApplicationController < ActionController::Base
33 | protect_from_forgery with: :null_session
34 | end
35 |
36 | class TodosController < ApplicationController
37 | before_action :set_todo, only: [:show, :update, :destroy, :complete]
38 |
39 | def index
40 | @todos = Todo.all
41 | render json: @todos
42 | end
43 |
44 | def show
45 | render json: @todo
46 | end
47 |
48 | def create
49 | @todo = Todo.new(todo_params)
50 | if @todo.save
51 | render json: @todo, status: :created, location: @todo
52 | else
53 | render json: @todo.errors, status: :unprocessable_entity
54 | end
55 | end
56 |
57 | def update
58 | if @todo.update(todo_params)
59 | render json: @todo
60 | else
61 | render json: @todo.errors, status: :unprocessable_entity
62 | end
63 | end
64 |
65 | def complete
66 | if @todo.complete
67 | render json: @todo
68 | else
69 | render json: @todo.errors, status: :unprocessable_entity
70 | end
71 | end
72 |
73 | def destroy
74 | @todo.destroy
75 | head :no_content
76 | end
77 |
78 | private
79 |
80 | def set_todo
81 | @todo = Todo.find(params[:id])
82 | end
83 |
84 | def todo_params
85 | params.require(:todo).permit(:name, :status)
86 | end
87 | end
88 |
89 | UniRails::App.configure do
90 | config.log_level = :error
91 | config.hosts << ENV['APP_HOST']
92 | end
93 |
94 | UniRails.run(Port: 3000, BindAddress: '0.0.0.0')
95 |
--------------------------------------------------------------------------------
/features/todos-api/app_test.rb:
--------------------------------------------------------------------------------
1 | require "capybara"
2 | require "capybara/dsl"
3 | require 'capybara/minitest'
4 | require "selenium-webdriver"
5 | require "minitest/autorun"
6 | require "debug"
7 |
8 | APP_HOST = ENV.fetch('APP_HOST')
9 |
10 | def truncate_tables
11 | require "sqlite3"
12 | SQLite3::Database.new( "database.sqlite" ) do |db|
13 | db.execute("DELETE FROM todos")
14 | end
15 | end
16 |
17 | class Client
18 | def initialize(url)
19 | @uri = URI(url)
20 | end
21 |
22 | def get(url)
23 | start do |http|
24 | http.get(url)
25 | end
26 | end
27 |
28 | def post(url, params)
29 | start do |http|
30 | http.post(url, params.to_json, "Content-Type" => "application/json")
31 | end
32 | end
33 |
34 | def delete(url)
35 | start do |http|
36 | http.delete(url)
37 | end
38 | end
39 |
40 | def start
41 | Net::HTTP.start(@uri.hostname, @uri.port) do |http|
42 | yield http
43 | end
44 | end
45 | end
46 |
47 | class TestJSONTodos < Minitest::Test
48 | def setup
49 | @client = Client.new(APP_HOST)
50 | truncate_tables
51 | end
52 |
53 | def test_create_a_new_todo
54 | response = @client.get('/todos.json')
55 | todos = JSON.parse(response.body)
56 | assert_equal 0, todos.length
57 |
58 | response = @client.post '/todos.json', { todo: { name: 'Buy milk' } }
59 | todo = JSON.parse(response.body)
60 |
61 | response = @client.get('/todos.json')
62 | todos = JSON.parse(response.body)
63 | assert_equal 1, todos.length
64 |
65 | @client.delete("/todos/#{todo['id']}.json")
66 |
67 | response = @client.get('/todos.json')
68 | todos = JSON.parse(response.body)
69 | assert_equal 0, todos.length
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/features/todos-api/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | uni_rails:
3 | build: .
4 | ports:
5 | - 127.0.0.1:3000:3000
6 | volumes:
7 | - .:/usr/src/app
8 | environment:
9 | - APP_HOST=uni_rails:3000
10 | - DATABASE_URL=sqlite3:////usr/src/app/database.sqlite
11 | - SECRET_KEY_BASE=whatever
12 | healthcheck:
13 | test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
14 | interval: 3s
15 | timeout: 5s
16 | retries: 3
17 | start_period: 5s
18 | command: ruby app.rb
19 | test:
20 | build: .
21 | volumes:
22 | - .:/usr/src/app
23 | environment:
24 | - APP_HOST=http://uni_rails:3000
25 | depends_on:
26 | uni_rails:
27 | condition: service_healthy
28 | command: ruby app_test.rb
29 |
--------------------------------------------------------------------------------
/features/todos-hotwire/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.3.0-slim-bullseye
2 |
3 | ARG BUILD_PACKAGES="build-essential git curl libvips pkg-config sqlite3 libsqlite3-dev"
4 |
5 | # Install packages needed to build gems
6 | RUN apt-get update -qq && \
7 | apt-get install --no-install-recommends -y $BUILD_PACKAGES
8 |
9 | WORKDIR /usr/src/app
10 |
11 | ENV LANG C.UTF-8
12 | ENV BUNDLER_VERSION 2.1
13 | ENV GEM_HOME="/usr/local/bundle"
14 | ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
15 |
16 | COPY Gemfile uni_rails.gem ./
17 | RUN gem install uni_rails.gem
18 | RUN bundle install
19 |
20 | COPY . /usr/src/app
21 |
22 | CMD ["ruby", "app.rb"]
--------------------------------------------------------------------------------
/features/todos-hotwire/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | gem 'uni_rails', path: '/usr/local/bundle'
6 | gem "sqlite3", "~> 1.7"
7 |
8 | # Test
9 | gem "capybara", "~> 3.40"
10 | gem "selenium-webdriver", "~> 4.19"
11 | gem "minitest"
12 | gem "debug"
13 |
--------------------------------------------------------------------------------
/features/todos-hotwire/app.rb:
--------------------------------------------------------------------------------
1 | require 'uni_rails'
2 | require 'debug'
3 | require 'sqlite3'
4 |
5 | ActiveRecord::Base.establish_connection
6 | ActiveRecord::Schema.define do
7 | create_table :todos, force: :cascade do |t|
8 | t.string :name
9 | t.integer :status, default: 0
10 | t.timestamps
11 | end
12 | end
13 |
14 | UniRails.enable_turbo_rails!
15 |
16 | UniRails.routes do
17 | root 'todos#index'
18 | resources :todos do
19 | patch :change_status, on: :member
20 | end
21 | end
22 |
23 | # MODELS
24 |
25 | class Todo < ActiveRecord::Base
26 | enum status: {
27 | incomplete: 0,
28 | complete: 1
29 | }
30 |
31 | validates :name, presence: true
32 | end
33 |
34 |
35 | # CONTROLLERS
36 |
37 | class TodosController < ActionController::Base
38 | layout 'application'
39 |
40 | before_action :set_todo, only: [:edit, :update, :destroy, :change_status]
41 |
42 | def index
43 | @todos = Todo.where(status: params[:status].presence || 'incomplete')
44 | end
45 |
46 | def new
47 | @todo = Todo.new
48 | end
49 |
50 | def edit
51 | @todo = Todo.find(params[:id])
52 | end
53 |
54 | def create
55 | @todo = Todo.new(todo_params)
56 |
57 | respond_to do |format|
58 | if @todo.save
59 | format.turbo_stream
60 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully created." }
61 | else
62 | format.turbo_stream { render turbo_stream: turbo_stream.replace("#{helpers.dom_id(@todo)}_form", partial: "form", locals: { todo: @todo }) }
63 | format.html { render :new, status: :unprocessable_entity }
64 | end
65 | end
66 | end
67 |
68 | def update
69 | respond_to do |format|
70 | if @todo.update(todo_params)
71 | format.turbo_stream
72 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully updated." }
73 | format.json { render :show, status: :ok, location: @todo }
74 | else
75 | format.turbo_stream { render turbo_stream: turbo_stream.replace("#{helpers.dom_id(@todo)}_form", partial: "form", locals: { todo: @todo }) }
76 | format.html { render :edit, status: :unprocessable_entity }
77 | format.json { render json: @todo.errors, status: :unprocessable_entity }
78 | end
79 | end
80 | end
81 |
82 | def destroy
83 | @todo.destroy
84 |
85 | respond_to do |format|
86 | format.turbo_stream { render turbo_stream: turbo_stream.remove("#{helpers.dom_id(@todo)}_container") }
87 | format.html { redirect_to todos_url, notice: "Todo was successfully destroyed." }
88 | format.json { head :no_content }
89 | end
90 | end
91 |
92 | def change_status
93 | @todo.update(status: todo_params[:status])
94 | respond_to do |format|
95 | format.turbo_stream { render turbo_stream: turbo_stream.remove("#{helpers.dom_id(@todo)}_container") }
96 | format.html { redirect_to todos_path, notice: "Updated todo status." }
97 | end
98 | end
99 |
100 | private
101 |
102 | def set_todo
103 | @todo = Todo.find(params[:id])
104 | end
105 |
106 | def todo_params
107 | params.require(:todo).permit(:name, :status)
108 | end
109 | end
110 |
111 | # VIEWS
112 |
113 | UniRails.javascript <<~JAVASCRIPT
114 | import * as Turbo from "turbo"
115 | JAVASCRIPT
116 |
117 | UniRails.register_view "layouts/application.html.erb", <<~'HTML'
118 |
119 |
120 |
121 | Template
122 |
123 | <%= csrf_meta_tags %>
124 | <%= csp_meta_tag %>
125 |
126 | <%= uni_rails_css_stylesheet %>
127 | <%= uni_rails_import_map_tag %>
128 | <%= uni_rails_javascript_script %>
129 |
130 |
131 |
132 | <%= yield %>
133 |
134 |
135 | HTML
136 |
137 | UniRails.register_view "todos/index.html.erb", <<~'HTML'
138 |
139 |
140 | Your todos
141 |
142 | <%= turbo_frame_tag "todos-container", class: "block max-w-2xl w-full bg-gray-100 py-8 px-4 border border-gray-200 rounded shadow-sm" do %>
143 |
144 |
145 | -
146 | <%= link_to "Incomplete",
147 | todos_path(status: "incomplete"),
148 | id: "nav-bar-action-incomplete",
149 | class: "inline-block py-4 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300"
150 | %>
151 |
-
152 | <%= link_to "Complete",
153 | todos_path(status: "complete"),
154 | id: "nav-bar-action-complete",
155 | class: "inline-block py-4 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300"
156 | %>
157 |
158 |
159 |
160 | <% unless params[:status] == "complete" %>
161 |
162 | <%= render "form", todo: Todo.new %>
163 |
164 | <% end %>
165 |
166 | <%= render @todos %>
167 |
168 | <% end %>
169 |
170 | HTML
171 |
172 | UniRails.register_view "todos/_form.html.erb", <<~'HTML'
173 | <%= form_with(model: todo, id: "#{dom_id(todo)}_form") do |form| %>
174 | <% if todo.errors.any? %>
175 |
176 |
<%= pluralize(todo.errors.count, "error") %> prohibited this todo from being saved:
177 |
178 |
179 | <% todo.errors.each do |error| %>
180 | - <%= error.full_message %>
181 | <% end %>
182 |
183 |
184 | <% end %>
185 |
186 | <%= form.label :name, class: "sr-only" %>
187 | <%= form.text_field :name, class: "block w-full rounded-none rounded-l-md sm:text-sm border-gray-300", placeholder: "Add a new todo..." %>
188 | <%= form.submit class: "-ml-px relative px-4 py-2 border border-blue-600 text-sm font-medium rounded-r-md text-white bg-blue-600 hover:bg-blue-700" %>
189 |
190 | <% end %>
191 | HTML
192 |
193 | UniRails.register_view "todos/_todo.html.erb", <<~'HTML'
194 | " class="py-2 px-4 border-b border-gray-300">
195 | <%= turbo_frame_tag dom_id(todo) do %>
196 |
197 | <%= link_to todo.name, edit_todo_path(todo), class: todo.complete? ? 'line-through' : '' %>
198 |
199 |
200 | <% if todo.complete? %>
201 | <%= button_to change_status_todo_path(todo, todo: { status: 'incomplete' }), class: "bg-green-600 px-4 py-2 rounded hover:bg-green-700", method: :patch do %>
202 |
Mark as incomplete
203 |
206 | <% end %>
207 | <% else %>
208 | <%= button_to change_status_todo_path(todo, todo: { status: 'complete' }), class: "bg-gray-400 px-4 py-2 rounded hover:bg-green-700", method: :patch do %>
209 |
Mark as complete
210 |
213 | <% end %>
214 | <% end %>
215 |
216 | <%= button_to todo_path(todo), class: "bg-red-600 px-4 py-2 rounded hover:bg-red-700", method: :delete do %>
217 |
Delete
218 |
221 | <% end %>
222 |
223 |
224 | <% end %>
225 |
226 | HTML
227 |
228 | UniRails.register_view "todos/edit.html.erb", <<~'HTML'
229 | <%= turbo_frame_tag dom_id(@todo) do %>
230 | <%= render "form", todo: @todo %>
231 | <% end %>
232 | HTML
233 |
234 | UniRails.register_view "todos/create.turbo_stream.erb", <<~'HTML'
235 | <%= turbo_stream.prepend "todos" do %>
236 | <%= render "todo", todo: @todo %>
237 | <% end %>
238 | <%= turbo_stream.replace "#{dom_id(Todo.new)}_form" do %>
239 | <%= render "form", todo: Todo.new %>
240 | <% end %>
241 | HTML
242 |
243 | UniRails.register_view "todos/update.turbo_stream.erb", <<~'HTML'
244 | <%= turbo_stream.replace "#{dom_id(@todo)}_container" do %>
245 | <%= render "todo", todo: @todo %>
246 | <% end %>
247 | HTML
248 |
249 | UniRails::App.configure do
250 | config.log_level = :error
251 | config.hosts << ENV['APP_HOST']
252 | end
253 |
254 | UniRails.run(Port: 3000, BindAddress: '0.0.0.0')
255 |
--------------------------------------------------------------------------------
/features/todos-hotwire/app_test.rb:
--------------------------------------------------------------------------------
1 | require "capybara"
2 | require "capybara/dsl"
3 | require 'capybara/minitest'
4 | require "selenium-webdriver"
5 | require "minitest/autorun"
6 | require "debug"
7 |
8 | APP_HOST = ENV.fetch('APP_HOST')
9 |
10 | Capybara.register_driver :selenium_remote_chrome do |app|
11 | options = Selenium::WebDriver::Chrome::Options.new
12 | # Add any additional options you need
13 | options.add_argument('--headless') # Uncomment if you want to run headless mode
14 |
15 | Capybara::Selenium::Driver.new(app,
16 | browser: :remote,
17 | url: "http://selenium:4444/wd/hub",
18 | options: options
19 | )
20 | end
21 |
22 | Capybara.default_driver = :selenium_remote_chrome
23 | Capybara.app_host = APP_HOST
24 | Capybara.default_max_wait_time = 5
25 |
26 | def truncate_tables
27 | require "sqlite3"
28 | SQLite3::Database.new( "database.sqlite" ) do |db|
29 | db.execute("DELETE FROM todos")
30 | end
31 | end
32 |
33 | class TestNewTodos < Minitest::Test
34 | include Capybara::DSL
35 | include Capybara::Minitest::Assertions
36 |
37 | def setup
38 | truncate_tables
39 | end
40 |
41 | def teardown
42 | Capybara.reset_sessions!
43 | Capybara.use_default_driver
44 | end
45 |
46 | def test_create_a_new_todo
47 | visit "/"
48 |
49 | fill_in 'Name', with: 'Buy milk'
50 | click_on 'Create Todo'
51 | assert_text 'Buy milk'
52 |
53 | within first('ul#todos li') do
54 | click_on 'Buy milk'
55 | fill_in 'Name', with: 'Buy milk (updated)'
56 | click_on 'Update Todo'
57 | end
58 |
59 | assert_text 'Buy milk (updated)'
60 | click_on 'Mark as complete'
61 |
62 | assert_incomplete(0)
63 | assert_complete(1)
64 |
65 | click_on 'Complete'
66 | assert_text 'Buy milk (updated)'
67 |
68 | within first('ul#todos li') do
69 | click_on 'Mark as incomplete'
70 | end
71 |
72 | assert_incomplete(1)
73 | assert_complete(0)
74 |
75 | click_on 'Incomplete'
76 | assert_text 'Buy milk (updated)'
77 |
78 | within first('ul#todos li') do
79 | click_on 'Delete'
80 | end
81 |
82 | assert_complete(0)
83 | assert_incomplete(0)
84 | end
85 |
86 | private
87 |
88 | def assert_complete(n)
89 | find('#nav-bar-action-complete').click
90 | assert_equal n, all('ul#todos li').length
91 | end
92 |
93 | def assert_incomplete(n)
94 | find('#nav-bar-action-incomplete').click
95 | assert_equal n, all('ul#todos li').length
96 | end
97 | end
98 |
99 |
--------------------------------------------------------------------------------
/features/todos-hotwire/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | uni_rails:
3 | build: .
4 | ports:
5 | - 127.0.0.1:3000:3000
6 | volumes:
7 | - .:/usr/src/app
8 | environment:
9 | - APP_HOST=uni_rails:3000
10 | - DATABASE_URL=sqlite3:////usr/src/app/database.sqlite
11 | - SECRET_KEY_BASE=whatever
12 | healthcheck:
13 | test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
14 | interval: 3s
15 | timeout: 5s
16 | retries: 3
17 | start_period: 5s
18 | command: ruby app.rb
19 | test:
20 | build: .
21 | volumes:
22 | - .:/usr/src/app
23 | environment:
24 | - APP_HOST=http://uni_rails:3000
25 | depends_on:
26 | uni_rails:
27 | condition: service_healthy
28 | selenium:
29 | condition: service_healthy
30 | command: ruby app_test.rb
31 | selenium:
32 | image: selenium/standalone-chrome:latest
33 | ports:
34 | - "4444:4444"
35 | - "7900:7900"
36 | healthcheck:
37 | test: ["CMD-SHELL", "curl -f http://localhost:4444/status || exit 1"]
38 | interval: 3s
39 | timeout: 5s
40 | retries: 3
41 | start_period: 5s
42 |
--------------------------------------------------------------------------------
/features/todos-scaffold/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexB52/uni_rails/2fa0563f39433d7236edfdeefa02d301fb793425/features/todos-scaffold/.DS_Store
--------------------------------------------------------------------------------
/features/todos-scaffold/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.3.0-slim-bullseye
2 |
3 | ARG BUILD_PACKAGES="build-essential git curl libvips pkg-config sqlite3 libsqlite3-dev"
4 |
5 | # Install packages needed to build gems
6 | RUN apt-get update -qq && \
7 | apt-get install --no-install-recommends -y $BUILD_PACKAGES
8 |
9 | WORKDIR /usr/src/app
10 |
11 | ENV LANG C.UTF-8
12 | ENV BUNDLER_VERSION 2.1
13 | ENV GEM_HOME="/usr/local/bundle"
14 | ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
15 |
16 | COPY Gemfile uni_rails.gem ./
17 | RUN gem install uni_rails.gem
18 | RUN bundle install
19 |
20 | COPY . /usr/src/app
21 |
22 | CMD ["ruby", "app.rb"]
--------------------------------------------------------------------------------
/features/todos-scaffold/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | gem 'uni_rails', path: '/usr/local/bundle'
6 | gem "sqlite3", "~> 1.7"
7 | gem "jbuilder"
8 |
9 | # Test
10 | gem "capybara", "~> 3.40"
11 | gem "selenium-webdriver", "~> 4.19"
12 | gem "minitest"
13 | gem "debug"
14 |
--------------------------------------------------------------------------------
/features/todos-scaffold/app.rb:
--------------------------------------------------------------------------------
1 | require "uni_rails"
2 | require "jbuilder"
3 | require "sqlite3"
4 | require "debug"
5 |
6 | ActiveRecord::Base.establish_connection
7 | ActiveRecord::Schema.define do
8 | create_table :todos, force: :cascade do |t|
9 | t.string :name
10 | t.datetime :completed_at
11 | t.timestamps
12 | end
13 | end
14 |
15 | UniRails.routes do
16 | root 'todos#index'
17 | resources :todos
18 | end
19 |
20 | # MODELS
21 |
22 | class Todo < ActiveRecord::Base
23 | validates :name, presence: true
24 |
25 | def complete(at: Time.zone.now)
26 | update(completed_at: at)
27 | end
28 | end
29 |
30 |
31 | # CONTROLLERS
32 | class ApplicationController < ActionController::Base
33 | protect_from_forgery with: :null_session, unless: :csrf_required?
34 |
35 | private
36 |
37 | def csrf_required?
38 | !request.format.json?
39 | end
40 | end
41 |
42 | class TodosController < ApplicationController
43 | before_action :set_todo, only: %i[ show edit update destroy ]
44 |
45 | # GET /todos or /todos.json
46 | def index
47 | @todos = Todo.all
48 | end
49 |
50 | # GET /todos/1 or /todos/1.json
51 | def show
52 | end
53 |
54 | # GET /todos/new
55 | def new
56 | @todo = Todo.new
57 | end
58 |
59 | # GET /todos/1/edit
60 | def edit
61 | end
62 |
63 | # POST /todos or /todos.json
64 | def create
65 | @todo = Todo.new(todo_params)
66 |
67 | respond_to do |format|
68 | if @todo.save
69 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully created." }
70 | format.json { render :show, status: :created, location: @todo }
71 | else
72 | format.html { render :new, status: :unprocessable_entity }
73 | format.json { render json: @todo.errors, status: :unprocessable_entity }
74 | end
75 | end
76 | end
77 |
78 | # PATCH/PUT /todos/1 or /todos/1.json
79 | def update
80 | respond_to do |format|
81 | if @todo.update(todo_params)
82 | format.html { redirect_to todo_url(@todo), notice: "Todo was successfully updated." }
83 | format.json { render :show, status: :ok, location: @todo }
84 | else
85 | format.html { render :edit, status: :unprocessable_entity }
86 | format.json { render json: @todo.errors, status: :unprocessable_entity }
87 | end
88 | end
89 | end
90 |
91 | # DELETE /todos/1 or /todos/1.json
92 | def destroy
93 | @todo.destroy!
94 |
95 | respond_to do |format|
96 | format.html { redirect_to todos_url, notice: "Todo was successfully destroyed." }
97 | format.json { head :no_content }
98 | end
99 | end
100 |
101 | private
102 | # Use callbacks to share common setup or constraints between actions.
103 | def set_todo
104 | @todo = Todo.find(params[:id])
105 | end
106 |
107 | # Only allow a list of trusted parameters through.
108 | def todo_params
109 | params.require(:todo).permit(:name, :completed_at)
110 | end
111 | end
112 |
113 | # VIEWS - HTML
114 |
115 | UniRails.register_view "todos/_form.html.erb", <<~HTML
116 | <%= form_with(model: todo) do |form| %>
117 | <% if todo.errors.any? %>
118 |
119 |
<%= pluralize(todo.errors.count, "error") %> prohibited this todo from being saved:
120 |
121 |
122 | <% todo.errors.each do |error| %>
123 | - <%= error.full_message %>
124 | <% end %>
125 |
126 |
127 | <% end %>
128 |
129 |
130 | <%= form.label :name, style: "display: block" %>
131 | <%= form.text_field :name %>
132 |
133 |
134 |
135 | <%= form.label :completed_at, style: "display: block" %>
136 | <%= form.datetime_field :completed_at %>
137 |
138 |
139 |
140 | <%= form.submit %>
141 |
142 | <% end %>
143 | HTML
144 |
145 | UniRails.register_view "todos/_todo.html.erb", <<~HTML
146 |
147 |
148 | Name:
149 | <%= todo.name %>
150 |
151 |
152 |
153 | Completed at:
154 | <%= todo.completed_at %>
155 |
156 |
157 |
158 | HTML
159 |
160 | UniRails.register_view "todos/edit.html.erb", <<~HTML
161 | Editing todo
162 |
163 | <%= render "form", todo: @todo %>
164 |
165 |
166 |
167 |
168 | <%= link_to "Show this todo", @todo %> |
169 | <%= link_to "Back to todos", todos_path %>
170 |
171 | HTML
172 |
173 | UniRails.register_view "todos/index.html.erb", <<~HTML
174 | <%= notice %>
175 |
176 | Todos
177 |
178 |
179 | <% @todos.each do |todo| %>
180 | <%= render todo %>
181 |
182 | <%= link_to "Show this todo", todo %>
183 |
184 | <% end %>
185 |
186 |
187 | <%= link_to "New todo", new_todo_path %>
188 | HTML
189 |
190 | UniRails.register_view "todos/new.html.erb", <<~HTML
191 | New todo
192 |
193 | <%= render "form", todo: @todo %>
194 |
195 |
196 |
197 |
198 | <%= link_to "Back to todos", todos_path %>
199 |
200 | HTML
201 |
202 | UniRails.register_view "todos/show.html.erb", <<~HTML
203 | <%= notice %>
204 |
205 | <%= render @todo %>
206 |
207 |
208 | <%= link_to "Edit this todo", edit_todo_path(@todo) %> |
209 | <%= link_to "Back to todos", todos_path %>
210 |
211 | <%= button_to "Destroy this todo", @todo, method: :delete %>
212 |
213 | HTML
214 |
215 | # VIEWS - JSON
216 |
217 | UniRails.register_view "todos/_todo.json.jbuilder", <<~RUBY
218 | json.extract! todo, :id, :name, :completed_at, :created_at, :updated_at
219 | json.url todo_url(todo, format: :json)
220 | RUBY
221 |
222 | UniRails.register_view "todos/index.json.jbuilder", <<~RUBY
223 | json.array! @todos, partial: "todos/todo", as: :todo
224 | RUBY
225 |
226 | UniRails.register_view "todos/show.json.jbuilder", <<~RUBY
227 | json.partial! "todos/todo", todo: @todo
228 | RUBY
229 |
230 | UniRails::App.configure do
231 | config.log_level = :error
232 | config.hosts << ENV['APP_HOST']
233 | end
234 |
235 | UniRails.run(Port: 3000, BindAddress: '0.0.0.0')
236 |
--------------------------------------------------------------------------------
/features/todos-scaffold/app_test.rb:
--------------------------------------------------------------------------------
1 | require "capybara"
2 | require "capybara/dsl"
3 | require 'capybara/minitest'
4 | require "selenium-webdriver"
5 | require "minitest/autorun"
6 | require "debug"
7 |
8 | APP_HOST = ENV.fetch('APP_HOST')
9 |
10 | Capybara.register_driver :selenium_remote_chrome do |app|
11 | options = Selenium::WebDriver::Chrome::Options.new
12 | # Add any additional options you need
13 | options.add_argument('--headless') # Uncomment if you want to run headless mode
14 |
15 | Capybara::Selenium::Driver.new(app,
16 | browser: :remote,
17 | url: "http://selenium:4444/wd/hub",
18 | options: options
19 | )
20 | end
21 |
22 | Capybara.default_driver = :selenium_remote_chrome
23 | Capybara.app_host = APP_HOST
24 |
25 | def truncate_tables
26 | require "sqlite3"
27 | SQLite3::Database.new( "database.sqlite" ) do |db|
28 | db.execute("DELETE FROM todos")
29 | end
30 | end
31 |
32 | class Client
33 | def initialize(url)
34 | @uri = URI(url)
35 | end
36 |
37 | def get(url)
38 | start do |http|
39 | http.get(url)
40 | end
41 | end
42 |
43 | def post(url, params)
44 | start do |http|
45 | http.post(url, params.to_json, "Content-Type" => "application/json")
46 | end
47 | end
48 |
49 | def delete(url)
50 | start do |http|
51 | http.delete(url)
52 | end
53 | end
54 |
55 | def start
56 | Net::HTTP.start(@uri.hostname, @uri.port) do |http|
57 | yield http
58 | end
59 | end
60 | end
61 |
62 | class TestJSONTodos < Minitest::Test
63 | def setup
64 | @client = Client.new(APP_HOST)
65 | truncate_tables
66 | end
67 |
68 | def test_create_a_new_todo
69 | response = @client.get('/todos.json')
70 | todos = JSON.parse(response.body)
71 | assert_equal 0, todos.length
72 |
73 | response = @client.post '/todos.json', { todo: { name: 'Buy milk' } }
74 | todo = JSON.parse(response.body)
75 |
76 | response = @client.get('/todos.json')
77 | todos = JSON.parse(response.body)
78 | assert_equal 1, todos.length
79 |
80 | @client.delete("/todos/#{todo['id']}.json")
81 |
82 | response = @client.get('/todos.json')
83 | todos = JSON.parse(response.body)
84 | assert_equal 0, todos.length
85 | end
86 | end
87 |
88 | class TestHTMLTodos < Minitest::Test
89 | include Capybara::DSL
90 | include Capybara::Minitest::Assertions
91 |
92 | def setup
93 | truncate_tables
94 | end
95 |
96 | def test_create_a_new_todo
97 | visit "/"
98 |
99 | click_on 'New todo'
100 | assert_text 'New todo'
101 |
102 | fill_in 'Name', with: 'Buy milk'
103 | click_on 'Create Todo'
104 | assert_text 'Todo was successfully created.'
105 |
106 | click_on 'Edit this todo'
107 | fill_in 'Name', with: 'Buy milk (updated)'
108 | click_on 'Update Todo'
109 | assert_text 'Buy milk (updated)'
110 | assert_text 'Todo was successfully updated.'
111 |
112 | click_on 'Destroy this todo'
113 | assert_text 'Todo was successfully destroyed.'
114 |
115 | assert_equal '/todos', page.current_path
116 | end
117 |
118 | def test_multiple_todos
119 | skip
120 | create_todo(name: 'Buy Shampoo')
121 | create_todo(name: 'Buy Timtam')
122 | end
123 |
124 | def teardown
125 | Capybara.reset_sessions!
126 | Capybara.use_default_driver
127 | end
128 |
129 | private
130 |
131 | def cleanup_todos
132 | visit '/'
133 | until (links = all('a', text: 'Show this todo')).empty? do
134 | links.first.click
135 | click_on 'Destroy this todo'
136 | assert_text 'Todo was successfully destroyed.'
137 | end
138 | end
139 |
140 | def create_todo(name:)
141 | visit '/todos/new'
142 | fill_in 'Name', with: name
143 | click_on 'Create'
144 | assert_text 'Todo was successfully created.'
145 | end
146 | end
147 |
148 |
--------------------------------------------------------------------------------
/features/todos-scaffold/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | uni_rails:
3 | build: .
4 | ports:
5 | - 127.0.0.1:3000:3000
6 | volumes:
7 | - .:/usr/src/app
8 | environment:
9 | - APP_HOST=uni_rails:3000
10 | - DATABASE_URL=sqlite3:////usr/src/app/database.sqlite
11 | - SECRET_KEY_BASE=whatever
12 | healthcheck:
13 | test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
14 | interval: 3s
15 | timeout: 5s
16 | retries: 3
17 | start_period: 5s
18 | command: ruby app.rb
19 | test:
20 | build: .
21 | volumes:
22 | - .:/usr/src/app
23 | environment:
24 | - APP_HOST=http://uni_rails:3000
25 | depends_on:
26 | uni_rails:
27 | condition: service_healthy
28 | selenium:
29 | condition: service_healthy
30 | command: ruby app_test.rb
31 | selenium:
32 | image: selenium/standalone-chrome:latest
33 | ports:
34 | - "4444:4444"
35 | - "7900:7900"
36 | healthcheck:
37 | test: ["CMD-SHELL", "curl -f http://localhost:4444/status || exit 1"]
38 | interval: 3s
39 | timeout: 5s
40 | retries: 3
41 | start_period: 5s
42 |
--------------------------------------------------------------------------------
/lib/uni_rails.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | if ENV['SECRET_KEY_BASE'].nil?
4 | raise StandardError, <<~ERROR
5 |
6 | SECRET_KEY_BASE environment variable is required
7 | Provide ENV['SECRET_KEY_BASE'] in your file or export the variable to your profile
8 | ERROR
9 | end
10 |
11 | require "rails"
12 | require_relative "uni_rails/version"
13 | require_relative "uni_rails/helpers"
14 | require_relative "uni_rails/helpers/css_helper"
15 | require_relative "uni_rails/helpers/javascript_helper"
16 | require_relative "uni_rails/app"
17 | require_relative "uni_rails/app/css"
18 | require_relative "uni_rails/app/javascript"
19 | require_relative "uni_rails/app/views"
20 |
21 | module UniRails
22 | class Error < StandardError; end
23 |
24 | def self.enable_turbo_rails!
25 | # # We currently do not support ActionCable and ActiveJob
26 | require "turbo-rails"
27 |
28 | App.configure do
29 | initializer "turbo.no_action_cable", before: :set_eager_load_paths do
30 | unless defined?(ActionCable)
31 | Rails.autoloaders.once.do_not_eager_load("#{Turbo::Engine.root}/app/channels")
32 | end
33 |
34 | unless defined?(ActiveJob)
35 | Rails.autoloaders.once.do_not_eager_load("#{Turbo::Engine.root}/app/jobs")
36 | end
37 | end
38 | end
39 |
40 | App::Javascript.dependencies.merge!(
41 | "turbo" => "https://unpkg.com/@hotwired/turbo@8.0.4/dist/turbo.es2017-umd.js"
42 | )
43 | end
44 |
45 | def self.routes(&block)
46 | App.initializer :add_uni_routes, before: :add_internal_routes do |app|
47 | app.routes.prepend do
48 | get "/rails/info/routes" => "rails/info#routes", internal: true
49 | end
50 | app.routes.prepend(&block)
51 | end
52 | end
53 |
54 | def self.rackup_handler=(handler)
55 | @@rackup_handler = handler
56 | end
57 |
58 | def self.rackup_handler
59 | @@rackup_handler ||= begin
60 | require 'rackup'
61 | Rackup::Handler::WEBrick
62 | end
63 | end
64 |
65 | def self.register_view(action, view)
66 | UniRails::App::Views.instance.views[action] = view
67 | end
68 |
69 | def self.run(**webrick_options)
70 | App.initialize!
71 | rackup_handler.run App, **webrick_options
72 | # Rackup::Handler::Falcon.run App, **webrick_options
73 | # Rack::Handler::Puma.run App, **webrick_options
74 | # Rackup::Handler::WEBrick.run App, **webrick_options
75 | end
76 |
77 | def self.import_maps(dependencies)
78 | UniRails::App::Javascript.dependencies.merge! dependencies
79 | end
80 |
81 | def self.javascript(content)
82 | UniRails::App::Javascript.javascript = content
83 | end
84 |
85 | def self.css(content)
86 | UniRails::App::CSS.css = content
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/lib/uni_rails/app.rb:
--------------------------------------------------------------------------------
1 | # Pick the frameworks you want:
2 |
3 | require "active_model/railtie"
4 | # require "active_job/railtie"
5 | require "active_record/railtie" unless ENV['DATABASE_URL'].nil?
6 | # require "active_storage/engine"
7 | require "action_controller/railtie"
8 | # require "action_mailer/railtie"
9 | # require "action_mailbox/engine"
10 | # require "action_text/engine"
11 | require "action_view/railtie"
12 | # require "action_cable/engine"
13 | # require "rails/test_unit/railtie"
14 | require "action_view/testing/resolvers"
15 |
16 | module UniRails
17 | class App < Rails::Application
18 | config.load_defaults 7.2
19 |
20 | # config.eager_load = true
21 | config.logger = Logger.new(STDOUT)
22 | config.log_level = :debug
23 | config.secret_key_base = ENV.fetch('SECRET_KEY_BASE')
24 |
25 | routes.draw do
26 | resources :users
27 | end
28 |
29 | config.after_initialize do
30 | ActionController::Base.view_paths = Views.view_paths
31 | ActionController::Base.include App.routes.url_helpers
32 | ActionController::Base.helper Helpers::JavascriptHelper
33 | ActionController::Base.helper Helpers::CSSHelper
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/uni_rails/app/css.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | class App
3 | class CSS
4 | include Singleton
5 |
6 | class << self
7 | delegate :css, :css=, to: :instance
8 | end
9 |
10 | attr_accessor :css
11 | def initialize
12 | @css = ""
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/uni_rails/app/javascript.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | class App
3 | class Javascript
4 | include Singleton
5 |
6 | class << self
7 | delegate :imports,
8 | :dependencies, :dependencies=,
9 | :javascript, :javascript=,
10 | to: :instance
11 | end
12 |
13 | attr_accessor :dependencies, :javascript
14 | def initialize
15 | @dependencies = {}
16 | @javascript = ""
17 | end
18 |
19 | def imports
20 | { imports: dependencies }
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/uni_rails/app/views.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | class App
3 | class Views
4 | include Singleton
5 |
6 | DEFAULT_LAYOUT = <<~HTML
7 |
8 |
9 |
10 | Template
11 |
12 | <%= csrf_meta_tags %>
13 | <%= csp_meta_tag %>
14 |
15 | <%= uni_rails_css_stylesheet %>
16 | <%= uni_rails_import_map_tag %>
17 | <%= uni_rails_javascript_script %>
18 |
19 |
20 |
21 | <%= yield %>
22 |
23 |
24 | HTML
25 |
26 | attr_accessor :views
27 | def initialize
28 | @views = { 'layouts/application.html.erb' => DEFAULT_LAYOUT }
29 | end
30 |
31 | def self.view_paths
32 | [ActionView::FixtureResolver.new(instance.views)]
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/uni_rails/helpers.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | module Helpers
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/uni_rails/helpers/css_helper.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | module Helpers
3 | module CSSHelper
4 | def uni_rails_css_stylesheet
5 | content_tag(:style) do
6 | raw App::CSS.css
7 | end
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/uni_rails/helpers/javascript_helper.rb:
--------------------------------------------------------------------------------
1 | module UniRails
2 | module Helpers
3 | module JavascriptHelper
4 | def uni_rails_import_map_tag
5 | content_tag(:script, type: 'importmap') do
6 | raw App::Javascript.imports.to_json
7 | end
8 | end
9 |
10 | def uni_rails_javascript_script
11 | content_tag(:script, type: 'module') do
12 | raw App::Javascript.javascript
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/uni_rails/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module UniRails
4 | VERSION = "0.5.0"
5 | end
6 |
--------------------------------------------------------------------------------
/sig/uni_rails.rbs:
--------------------------------------------------------------------------------
1 | module UniRails
2 | VERSION: String
3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4 | end
5 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['SECRET_KEY_BASE'] = 'something'
4 |
5 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6 | require "uni_rails"
7 |
8 | require "minitest/autorun"
9 | require "debug"
10 |
11 |
--------------------------------------------------------------------------------
/test/test_uni_rails.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class TestUniRails < Minitest::Test
6 | def test_that_it_has_a_version_number
7 | refute_nil ::UniRails::VERSION
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/uni_rails.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "lib/uni_rails/version"
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "uni_rails"
7 | spec.version = UniRails::VERSION
8 | spec.authors = ["Alexandre Barret"]
9 | spec.email = ["barret.alx@gmail.com"]
10 |
11 | spec.summary = "A Ruby library to create single-file Rails application"
12 | spec.homepage = "https://github.com/AlexB52/uni_rails"
13 | spec.license = "MIT"
14 | spec.required_ruby_version = ">= 3.2.0"
15 |
16 | # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17 |
18 | spec.metadata["homepage_uri"] = spec.homepage
19 | spec.metadata["source_code_uri"] = "https://github.com/AlexB52/uni_rails"
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | spec.files = Dir.chdir(__dir__) do
24 | `git ls-files -z`.split("\x0").reject do |f|
25 | (File.expand_path(f) == __FILE__) ||
26 | f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
27 | end
28 | end
29 | spec.bindir = "exe"
30 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31 | spec.require_paths = ["lib"]
32 |
33 | # Uncomment to register a new dependency of your gem
34 | spec.add_dependency "rails", "~> 7.2.0"
35 | spec.add_dependency "rackup", "~> 2.1"
36 | spec.add_dependency "turbo-rails", "~> 2.0"
37 |
38 | # For more information and examples about making a new gem, check out our
39 | # guide at: https://bundler.io/guides/creating_gem.html
40 | end
41 |
--------------------------------------------------------------------------------