brut 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/CODE_OF_CONDUCT.txt +99 -0
- data/Dockerfile.dx +32 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +370 -0
- data/README.md +21 -0
- data/Rakefile +1 -0
- data/bin/bin_kit.rb +39 -0
- data/bin/rake +27 -0
- data/bin/setup +145 -0
- data/brut.gemspec +60 -0
- data/docker-compose.dx.yml +16 -0
- data/dx/build +26 -0
- data/dx/docker-compose.env +22 -0
- data/dx/dx.sh.lib +24 -0
- data/dx/exec +58 -0
- data/dx/prune +19 -0
- data/dx/setupkit.sh.lib +144 -0
- data/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/dx/start +30 -0
- data/dx/stop +23 -0
- data/lib/brut/back_end/action.rb +3 -0
- data/lib/brut/back_end/result.rb +46 -0
- data/lib/brut/back_end/seed_data.rb +24 -0
- data/lib/brut/back_end/validator.rb +3 -0
- data/lib/brut/back_end/validators/form_validator.rb +37 -0
- data/lib/brut/cli/app.rb +130 -0
- data/lib/brut/cli/app_runner.rb +219 -0
- data/lib/brut/cli/apps/build_assets.rb +123 -0
- data/lib/brut/cli/apps/db.rb +279 -0
- data/lib/brut/cli/apps/scaffold.rb +256 -0
- data/lib/brut/cli/apps/test.rb +200 -0
- data/lib/brut/cli/command.rb +130 -0
- data/lib/brut/cli/error.rb +12 -0
- data/lib/brut/cli/execution_results.rb +81 -0
- data/lib/brut/cli/executor.rb +37 -0
- data/lib/brut/cli/options.rb +46 -0
- data/lib/brut/cli/output.rb +30 -0
- data/lib/brut/cli.rb +24 -0
- data/lib/brut/factory_bot.rb +20 -0
- data/lib/brut/framework/app.rb +55 -0
- data/lib/brut/framework/config.rb +415 -0
- data/lib/brut/framework/container.rb +190 -0
- data/lib/brut/framework/errors/abstract_method.rb +9 -0
- data/lib/brut/framework/errors/bug.rb +14 -0
- data/lib/brut/framework/errors/not_found.rb +10 -0
- data/lib/brut/framework/errors.rb +14 -0
- data/lib/brut/framework/fussy_type_enforcement.rb +50 -0
- data/lib/brut/framework/mcp.rb +215 -0
- data/lib/brut/framework/project_environment.rb +18 -0
- data/lib/brut/framework.rb +13 -0
- data/lib/brut/front_end/asset_metadata.rb +76 -0
- data/lib/brut/front_end/component.rb +213 -0
- data/lib/brut/front_end/components/form_tag.rb +71 -0
- data/lib/brut/front_end/components/i18n_translations.rb +36 -0
- data/lib/brut/front_end/components/input.rb +13 -0
- data/lib/brut/front_end/components/inputs/csrf_token.rb +8 -0
- data/lib/brut/front_end/components/inputs/select.rb +100 -0
- data/lib/brut/front_end/components/inputs/text_field.rb +63 -0
- data/lib/brut/front_end/components/inputs/textarea.rb +51 -0
- data/lib/brut/front_end/components/locale_detection.rb +25 -0
- data/lib/brut/front_end/components/page_identifier.rb +13 -0
- data/lib/brut/front_end/components/timestamp.rb +33 -0
- data/lib/brut/front_end/download.rb +23 -0
- data/lib/brut/front_end/flash.rb +57 -0
- data/lib/brut/front_end/form.rb +171 -0
- data/lib/brut/front_end/forms/constraint_violation.rb +39 -0
- data/lib/brut/front_end/forms/input.rb +119 -0
- data/lib/brut/front_end/forms/input_definition.rb +100 -0
- data/lib/brut/front_end/forms/validity_state.rb +36 -0
- data/lib/brut/front_end/handler.rb +48 -0
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +11 -0
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +22 -0
- data/lib/brut/front_end/handling_results.rb +14 -0
- data/lib/brut/front_end/http_method.rb +33 -0
- data/lib/brut/front_end/http_status.rb +16 -0
- data/lib/brut/front_end/middleware.rb +7 -0
- data/lib/brut/front_end/middlewares/reload_app.rb +31 -0
- data/lib/brut/front_end/page.rb +47 -0
- data/lib/brut/front_end/request_context.rb +82 -0
- data/lib/brut/front_end/route_hook.rb +15 -0
- data/lib/brut/front_end/route_hooks/age_flash.rb +8 -0
- data/lib/brut/front_end/route_hooks/csp_no_inline_scripts.rb +17 -0
- data/lib/brut/front_end/route_hooks/csp_no_inline_styles_or_scripts.rb +46 -0
- data/lib/brut/front_end/route_hooks/locale_detection.rb +24 -0
- data/lib/brut/front_end/route_hooks/setup_request_context.rb +11 -0
- data/lib/brut/front_end/routing.rb +236 -0
- data/lib/brut/front_end/session.rb +56 -0
- data/lib/brut/front_end/template.rb +32 -0
- data/lib/brut/front_end/templates/block_filter.rb +60 -0
- data/lib/brut/front_end/templates/erb_engine.rb +26 -0
- data/lib/brut/front_end/templates/erb_parser.rb +84 -0
- data/lib/brut/front_end/templates/escapable_filter.rb +18 -0
- data/lib/brut/front_end/templates/html_safe_string.rb +40 -0
- data/lib/brut/i18n/base_methods.rb +168 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +4 -0
- data/lib/brut/i18n/http_accept_language.rb +68 -0
- data/lib/brut/i18n.rb +6 -0
- data/lib/brut/instrumentation/basic.rb +66 -0
- data/lib/brut/instrumentation/event.rb +19 -0
- data/lib/brut/instrumentation/http_event.rb +5 -0
- data/lib/brut/instrumentation/subscriber.rb +41 -0
- data/lib/brut/instrumentation.rb +11 -0
- data/lib/brut/junk_drawer.rb +88 -0
- data/lib/brut/sinatra_helpers.rb +183 -0
- data/lib/brut/spec_support/component_support.rb +49 -0
- data/lib/brut/spec_support/flash_support.rb +7 -0
- data/lib/brut/spec_support/general_support.rb +18 -0
- data/lib/brut/spec_support/handler_support.rb +7 -0
- data/lib/brut/spec_support/matcher.rb +9 -0
- data/lib/brut/spec_support/matchers/be_a_bug.rb +14 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +14 -0
- data/lib/brut/spec_support/matchers/be_routing_for.rb +11 -0
- data/lib/brut/spec_support/matchers/have_constraint_violation.rb +56 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +69 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +20 -0
- data/lib/brut/spec_support/matchers/have_returned_http_status.rb +27 -0
- data/lib/brut/spec_support/session_support.rb +3 -0
- data/lib/brut/spec_support.rb +12 -0
- data/lib/brut/version.rb +3 -0
- data/lib/brut.rb +38 -0
- data/lib/sequel/extensions/brut_instrumentation.rb +37 -0
- data/lib/sequel/extensions/brut_migrations.rb +98 -0
- data/lib/sequel/plugins/created_at.rb +14 -0
- data/lib/sequel/plugins/external_id.rb +45 -0
- data/lib/sequel/plugins/find_bang.rb +13 -0
- data/lib/sequel/plugins.rb +3 -0
- metadata +484 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require_relative "flash_support"
|
|
2
|
+
module Brut::SpecSupport::ComponentSupport
|
|
3
|
+
include Brut::SpecSupport::FlashSupport
|
|
4
|
+
include Brut::I18n::ForHTML
|
|
5
|
+
|
|
6
|
+
def render_and_parse(component,&block)
|
|
7
|
+
if component.kind_of?(Brut::FrontEnd::Page)
|
|
8
|
+
if !block.nil?
|
|
9
|
+
raise "pages do not accept blocks - do not pass one to render_and_parse"
|
|
10
|
+
end
|
|
11
|
+
result = component.handle!
|
|
12
|
+
case result
|
|
13
|
+
in String => html
|
|
14
|
+
Nokogiri::HTML5(html)
|
|
15
|
+
else
|
|
16
|
+
result
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
component.yielded_block = block
|
|
20
|
+
rendered_text = component.render
|
|
21
|
+
document = Nokogiri::HTML5(rendered_text)
|
|
22
|
+
component_html = document.css("body")
|
|
23
|
+
if component_html
|
|
24
|
+
non_blank_text_elements = component_html.children.select { |element|
|
|
25
|
+
if element.kind_of?(Nokogiri::XML::Text) && element.text.to_s.strip == ""
|
|
26
|
+
false
|
|
27
|
+
else
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
}
|
|
31
|
+
if non_blank_text_elements.size == 1
|
|
32
|
+
non_blank_text_elements[0]
|
|
33
|
+
else
|
|
34
|
+
raise "#{component.class} rendered #{non_blank_text_elements.size} elements other than blank text:\n\n#{non_blank_text_elements.map(&:name)}. Components should render a single element:\n#{rendered_text}"
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
raise "#{component.class} did not render HTML properly: #{rendered_text}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def routing_for(klass,**args)
|
|
43
|
+
Brut.container.routing.uri(klass,**args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def escape_html(...)
|
|
47
|
+
Brut::FrontEnd::Templates::EscapableFilter.escape_html(...)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Brut::SpecSupport::GeneralSupport
|
|
2
|
+
def self.included(mod)
|
|
3
|
+
mod.extend(ClassMethods)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def implementation_is_trivial
|
|
8
|
+
it "has no tests because the implementation is trivial" do
|
|
9
|
+
expect(true).to eq(true)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
def implementation_is_covered_by_other_tests(description)
|
|
13
|
+
it "has no tests because the implementation is sufficiently covered by other tests: #{description}" do
|
|
14
|
+
expect(true).to eq(true)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Brut::SpecSupport::Matchers
|
|
2
|
+
end
|
|
3
|
+
require_relative "matchers/have_constraint_violation"
|
|
4
|
+
require_relative "matchers/have_html_attribute"
|
|
5
|
+
require_relative "matchers/be_routing_for"
|
|
6
|
+
require_relative "matchers/be_page_for"
|
|
7
|
+
require_relative "matchers/have_rendered"
|
|
8
|
+
require_relative "matchers/have_returned_http_status"
|
|
9
|
+
require_relative "matchers/be_a_bug"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
RSpec::Matchers.define :be_a_bug do
|
|
2
|
+
match(:notify_expectation_failures => true) do |actual|
|
|
3
|
+
exception = nil
|
|
4
|
+
begin
|
|
5
|
+
actual.call
|
|
6
|
+
rescue => ex
|
|
7
|
+
exception = ex
|
|
8
|
+
end
|
|
9
|
+
expect(exception).not_to eq(nil),"Expected a bug, but no exception was thrown"
|
|
10
|
+
expect(exception).to be_kind_of(Brut::Framework::Errors::Bug)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
supports_block_expectations
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
RSpec::Matchers.define :be_page_for do |klass|
|
|
2
|
+
match do |page|
|
|
3
|
+
meta = page.locator("meta[name='class']")
|
|
4
|
+
expect(meta).to have_attribute("content", klass.name)
|
|
5
|
+
end
|
|
6
|
+
failure_message do |page|
|
|
7
|
+
meta = page.locator("meta[name='class']")
|
|
8
|
+
if meta.count == 0
|
|
9
|
+
"Could not find <meta name='class'> on the page:\n\n#{page.content}"
|
|
10
|
+
else
|
|
11
|
+
"Could not find <meta name='class' content='#{klass.name}'>, but found:\n\n#{meta.evaluate('e => e.outerHTML')}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
RSpec::Matchers.define :be_routing_for do |klass,**args|
|
|
2
|
+
match do |uri|
|
|
3
|
+
uri == Brut.container.routing.uri(klass,**args)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
failure_message do |uri|
|
|
7
|
+
expected = Brut.container.routing.uri(klass,**args)
|
|
8
|
+
"Expected route for #{klass}: #{expected}, but got #{uri}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_constraint_violation do |field,key:|
|
|
2
|
+
match do |form|
|
|
3
|
+
Brut::SpecSupport::Matchers::HaveConstraintViolation.new(form,field,key).matches?
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
failure_message do |form|
|
|
7
|
+
analysis = Brut::SpecSupport::Matchers::HaveConstraintViolation.new(form,field,key)
|
|
8
|
+
if analysis.found_field?
|
|
9
|
+
"#{field} did not have #{key} as a violation. These keys were found: #{analysis.keys_on_field_found.map(&:to_s).join(", ")}"
|
|
10
|
+
else
|
|
11
|
+
"#{field} had no errors. These fields DID: #{analysis.fields_found.map(&:to_s).join(", ")}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
failure_message_when_negated do |form|
|
|
16
|
+
"Found #{key} as a violation on #{field}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Brut::SpecSupport::Matchers::HaveConstraintViolation
|
|
21
|
+
attr_reader :fields_found
|
|
22
|
+
attr_reader :keys_on_field_found
|
|
23
|
+
|
|
24
|
+
def initialize(form, field, key)
|
|
25
|
+
if !form.kind_of?(Brut::FrontEnd::Form)
|
|
26
|
+
raise "#{self.class} only works with forms, not #{form.class}"
|
|
27
|
+
end
|
|
28
|
+
@form = form
|
|
29
|
+
@field = field.to_s
|
|
30
|
+
@key = key.to_s
|
|
31
|
+
|
|
32
|
+
@matches = false
|
|
33
|
+
@found_field = false
|
|
34
|
+
@fields_found = Set.new
|
|
35
|
+
@keys_on_field_found = Set.new
|
|
36
|
+
|
|
37
|
+
@form.constraint_violations.each do |input_name, constraint_violations|
|
|
38
|
+
if input_name.to_s == @field
|
|
39
|
+
@found_field = true
|
|
40
|
+
constraint_violations.each do |constraint_violation|
|
|
41
|
+
if constraint_violation.key.to_s == @key
|
|
42
|
+
@matches = true
|
|
43
|
+
else
|
|
44
|
+
@keys_on_field_found << constraint_violation.key.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
@fields_found << input_name.to_s
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def matches? = @matches
|
|
54
|
+
def found_field? = @found_field
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_html_attribute do |attribute|
|
|
2
|
+
if attribute.kind_of?(Hash)
|
|
3
|
+
if attribute.keys.length != 1
|
|
4
|
+
raise "have_html_attribute requires a single hash with a single key, or a single symbol/string. Received #{attribute.keys.length} keys: '#{attribute.keys.map(&:to_s).join(", ")}'"
|
|
5
|
+
end
|
|
6
|
+
elsif !attribute.kind_of?(Symbol) && !attribute.kind_of?(String)
|
|
7
|
+
raise "have_html_attribute requires a single hash with a single key, or a single symbol/string. Received a #{attribute.class}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
match do |result|
|
|
11
|
+
Brut::SpecSupport::Matchers::HaveHTMLAttribute.new(result,attribute).matches?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
failure_message do |result|
|
|
15
|
+
Brut::SpecSupport::Matchers::HaveHTMLAttribute.new(result,attribute).error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
failure_message_when_negated do |result|
|
|
19
|
+
if attribute.kind_of?(Hash)
|
|
20
|
+
"Found attribute '#{attribute.keys.first}' with value '#{attribute.values.first}'"
|
|
21
|
+
else
|
|
22
|
+
"Found attribute '#{attribute}' when not expecting it #{result.to_html}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Brut::SpecSupport::Matchers::HaveHTMLAttribute
|
|
28
|
+
|
|
29
|
+
attr_reader :error
|
|
30
|
+
|
|
31
|
+
def initialize(result, attribute)
|
|
32
|
+
@error = nil
|
|
33
|
+
if result.kind_of?(Nokogiri::XML::NodeSet)
|
|
34
|
+
if result.length > 1
|
|
35
|
+
@error = "Received #{result.length} matching nodes, when only one should've been returned"
|
|
36
|
+
elsif result.length == 0
|
|
37
|
+
@error = "Received no matching nodes to examine"
|
|
38
|
+
else
|
|
39
|
+
result = result.first
|
|
40
|
+
end
|
|
41
|
+
elsif (!result.kind_of?(Nokogiri::XML::Element))
|
|
42
|
+
@error = "Received a #{result.class} instead of a NodeSet or Element, as could be returned by `.css(...)`"
|
|
43
|
+
end
|
|
44
|
+
if !@error
|
|
45
|
+
if attribute.kind_of?(Hash)
|
|
46
|
+
attribute_name = attribute.keys.first.to_s
|
|
47
|
+
attribute_value = attribute.values.first.to_s
|
|
48
|
+
else
|
|
49
|
+
attribute_name = attribute.to_s
|
|
50
|
+
attribute_value = :any
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
nokogiri_attribute = result.attribute(attribute_name)
|
|
54
|
+
if nokogiri_attribute
|
|
55
|
+
if attribute_value != :any
|
|
56
|
+
found_value = result.attribute(attribute_name).value
|
|
57
|
+
|
|
58
|
+
if found_value != attribute_value
|
|
59
|
+
@error = "Value for '#{attribute_name}' was '#{found_value}'. Expected '#{attribute_value}'"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
else
|
|
63
|
+
@error = "Did not find attribute '#{attribute_name}' on element. Found: #{result.attributes.keys.join(", ")}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def matches? = @error.nil?
|
|
69
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_rendered do
|
|
2
|
+
match do |result|
|
|
3
|
+
result.kind_of?(String) || result.kind_of?(Brut::FrontEnd::Templates::HTMLSafeString)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
failure_message do |result|
|
|
7
|
+
case result
|
|
8
|
+
in URI => uri
|
|
9
|
+
"Got a redirect to #{uri} instead of rendering"
|
|
10
|
+
in Brut::FrontEnd::HttpStatus => http_status
|
|
11
|
+
"Got an HTTP status of #{http_status} instead of rendering"
|
|
12
|
+
else
|
|
13
|
+
"Got an unexpected result: #{result.class} instead of a String"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
failure_message_when_negated do |result|
|
|
17
|
+
"Result was rendered HTML instead of something else"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_returned_http_status do |http_status|
|
|
2
|
+
match do |result|
|
|
3
|
+
case result
|
|
4
|
+
in URI => uri
|
|
5
|
+
http_status == 302
|
|
6
|
+
in Brut::FrontEnd::HttpStatus => result_http_status
|
|
7
|
+
http_status == result_http_status.to_i
|
|
8
|
+
else
|
|
9
|
+
http_status == 200
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
failure_message do |result|
|
|
14
|
+
case result
|
|
15
|
+
in URI => uri
|
|
16
|
+
"Got a redirect (302) instead of a #{http_status}"
|
|
17
|
+
in Brut::FrontEnd::HttpStatus => result_http_status
|
|
18
|
+
"Got a #{result_http_status} instead of a #{http_status}"
|
|
19
|
+
else
|
|
20
|
+
"Got a render (200) instead of a #{http_status}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
failure_message_when_negated do |result|
|
|
24
|
+
"Result was rendered HTML instead of something else"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Convention here is different. We don't want to autoload
|
|
2
|
+
# a lot of stuff, since RSpec pollutes the Object namespace.
|
|
3
|
+
# Instead, we'll require that these files are required explicitly
|
|
4
|
+
module Brut
|
|
5
|
+
module SpecSupport
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
require_relative "spec_support/matcher"
|
|
9
|
+
require_relative "spec_support/component_support"
|
|
10
|
+
require_relative "spec_support/handler_support"
|
|
11
|
+
require_relative "spec_support/general_support"
|
|
12
|
+
require_relative "factory_bot"
|
data/lib/brut/version.rb
ADDED
data/lib/brut.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require_relative "brut/framework"
|
|
2
|
+
|
|
3
|
+
# Convention is as follows:
|
|
4
|
+
#
|
|
5
|
+
# * singluar thing is a class, the base class of others being used, e.g. the base page is called Brut::Page
|
|
6
|
+
# (e.g. and not Brut::Pages::Base).
|
|
7
|
+
module Brut
|
|
8
|
+
module FrontEnd
|
|
9
|
+
autoload(:AssetMetadata, "brut/front_end/asset_metadata")
|
|
10
|
+
autoload(:Component, "brut/front_end/component")
|
|
11
|
+
autoload(:Components, "brut/front_end/component")
|
|
12
|
+
autoload(:Download, "brut/front_end/download")
|
|
13
|
+
autoload(:Flash, "brut/front_end/flash")
|
|
14
|
+
autoload(:Form, "brut/front_end/form")
|
|
15
|
+
autoload(:Handler, "brut/front_end/handler")
|
|
16
|
+
autoload(:Handlers, "brut/front_end/handler")
|
|
17
|
+
autoload(:HandlingResults, "brut/front_end/handling_results")
|
|
18
|
+
autoload(:HttpMethod, "brut/front_end/http_method")
|
|
19
|
+
autoload(:HttpStatus, "brut/front_end/http_status")
|
|
20
|
+
autoload(:Middleware, "brut/front_end/middleware")
|
|
21
|
+
autoload(:Middlewares, "brut/front_end/middleware")
|
|
22
|
+
autoload(:Page, "brut/front_end/page")
|
|
23
|
+
autoload(:RequestContext, "brut/front_end/request_context")
|
|
24
|
+
autoload(:RouteHook, "brut/front_end/route_hook")
|
|
25
|
+
autoload(:RouteHooks, "brut/front_end/route_hook")
|
|
26
|
+
autoload(:Routing, "brut/front_end/routing")
|
|
27
|
+
autoload(:Session, "brut/front_end/session")
|
|
28
|
+
end
|
|
29
|
+
module BackEnd
|
|
30
|
+
autoload(:Result, "brut/back_end/result")
|
|
31
|
+
autoload(:Validators, "brut/back_end/validator")
|
|
32
|
+
end
|
|
33
|
+
# DO NOT autoload(:CLI) - that is intended to be require-able on its own
|
|
34
|
+
autoload(:I18n, "brut/i18n")
|
|
35
|
+
autoload(:Instrumentation,"brut/instrumentation")
|
|
36
|
+
autoload(:SinatraHelpers, "brut/sinatra_helpers")
|
|
37
|
+
end
|
|
38
|
+
require "sequel/plugins"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Extensions
|
|
3
|
+
module BrutInstrumentation
|
|
4
|
+
class Event < Brut::Instrumentation::Event
|
|
5
|
+
def initialize(operation:,sql:nil)
|
|
6
|
+
super(category: "sequel", name: operation, details: { sql: sql })
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
def execute(sql, opts = Sequel::OPTS, &block)
|
|
10
|
+
Brut.container.instrumentation.instrument(Event.new(operation: "execute", sql: sql)) do
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
def execute_dui(sql, opts = Sequel::OPTS, &block)
|
|
15
|
+
Brut.container.instrumentation.instrument(Event.new(operation: "execute_dui", sql: sql)) do
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
def execute_insert(sql, opts = Sequel::OPTS, &block)
|
|
20
|
+
Brut.container.instrumentation.instrument(Event.new(operation: "execute_insert", sql: sql)) do
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
def insert_select(*values)
|
|
25
|
+
Brut.container.instrumentation.instrument(Event.new(operation: "insert_select", sql: values)) do
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
def returning_fetch_rows(sql,&block)
|
|
30
|
+
Brut.container.instrumentation.instrument(Event.new(operation: "returning_fetch_rows", sql: sql)) do
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
Sequel::Dataset.register_extension(:brut_instrumentation, Sequel::Extensions::BrutInstrumentation)
|
|
37
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Extensions
|
|
3
|
+
# Enhancements to migrations to encourage better practices and reduce boilerplate.
|
|
4
|
+
#
|
|
5
|
+
# * If no primary key is specified, a primary key column named :id of type :int will be created
|
|
6
|
+
# * If no created_at is specified, a column name created_at of type timestamptz is created
|
|
7
|
+
# * create_table requires a comment: field
|
|
8
|
+
# * create_table accepts an external_id: true option that will create a unique citext called "external_id"
|
|
9
|
+
# * columns are non-null by default
|
|
10
|
+
# * foreign keys are non-null and an index is created
|
|
11
|
+
# * the `key` method allows specifying keys aka creating a unique constraint
|
|
12
|
+
module BrutMigrations
|
|
13
|
+
def create_table(*args)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
if args.last.is_a?(Hash)
|
|
17
|
+
if args.last[:comment]
|
|
18
|
+
run %{
|
|
19
|
+
comment on table #{args.first} is #{literal args.last[:comment]}
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "Table #{args.first} must have a comment"
|
|
23
|
+
end
|
|
24
|
+
if args.last[:external_id]
|
|
25
|
+
add_column args.first, :external_id, :citext, unique: true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_key(fields)
|
|
31
|
+
add_index fields, unique: true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_column(table,*args)
|
|
35
|
+
options = args.last
|
|
36
|
+
if options.is_a?(Hash)
|
|
37
|
+
if !options.key?(:null)
|
|
38
|
+
options[:null] = false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
super(table,*args)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_table_from_generator(name, generator, options)
|
|
45
|
+
if name != "schema_migrations"
|
|
46
|
+
if generator.columns.none? { |column| column[:primary_key] }
|
|
47
|
+
generator.primary_key :id
|
|
48
|
+
end
|
|
49
|
+
if generator.columns.none? { |column| column[:name].to_s == "created_at" }
|
|
50
|
+
generator.column :created_at, :timestamptz, null: false
|
|
51
|
+
end
|
|
52
|
+
generator.columns.each do |column|
|
|
53
|
+
if !column.key?(:null)
|
|
54
|
+
column[:null] = false
|
|
55
|
+
end
|
|
56
|
+
if column.key?(:table)
|
|
57
|
+
if !column.key?(:index)
|
|
58
|
+
column[:index] = true
|
|
59
|
+
generator.index(column[:name])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
Sequel::Database.register_extension(:brut_migrations) do |db|
|
|
70
|
+
db.extend Sequel::Extensions::BrutMigrations
|
|
71
|
+
class ::Sequel::Schema::CreateTableGenerator
|
|
72
|
+
def key(fields)
|
|
73
|
+
index fields, unique: true
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
class ::Sequel::Schema::AlterTableGenerator
|
|
77
|
+
def add_column_with_additions(name, type, opts={})
|
|
78
|
+
if !opts.key?(:null)
|
|
79
|
+
opts[:null] = false
|
|
80
|
+
end
|
|
81
|
+
add_column_base(name,type,opts)
|
|
82
|
+
end
|
|
83
|
+
alias_method :add_column_base, :add_column
|
|
84
|
+
alias_method :add_column, :add_column_with_additions
|
|
85
|
+
|
|
86
|
+
def add_foreign_key_with_additions(name, table, opts={})
|
|
87
|
+
if !opts.key?(:index)
|
|
88
|
+
opts[:index] = true
|
|
89
|
+
end
|
|
90
|
+
if !opts.key?(:null)
|
|
91
|
+
opts[:null] = false
|
|
92
|
+
end
|
|
93
|
+
add_foreign_key_base(name, table, opts)
|
|
94
|
+
end
|
|
95
|
+
alias_method :add_foreign_key_base, :add_foreign_key
|
|
96
|
+
alias_method :add_foreign_key, :add_foreign_key_with_additions
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
module ExternalId
|
|
4
|
+
def self.apply(model,*args,&block)
|
|
5
|
+
@global_prefix = (args.first || {})[:global_prefix]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_reader :global_prefix
|
|
10
|
+
def has_external_id(prefix)
|
|
11
|
+
global_prefix = find_global_prefix
|
|
12
|
+
@external_id_prefix = RichString.new("#{global_prefix}#{prefix}").to_s_or_nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def external_id_prefix = @external_id_prefix
|
|
16
|
+
|
|
17
|
+
def find_global_prefix(receiver=self)
|
|
18
|
+
if receiver.respond_to?(:global_prefix)
|
|
19
|
+
if receiver.global_prefix.nil?
|
|
20
|
+
receiver.ancestors.select { |ancestor| ancestor != receiver }.map { |ancestor|
|
|
21
|
+
self.find_global_prefix(ancestor)
|
|
22
|
+
}.compact.first
|
|
23
|
+
else
|
|
24
|
+
receiver.global_prefix
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module InstanceMethods
|
|
33
|
+
def before_save
|
|
34
|
+
if self.class.external_id_prefix
|
|
35
|
+
if self.external_id.nil?
|
|
36
|
+
random_hex = SecureRandom.hex
|
|
37
|
+
self.external_id = "#{self.class.external_id_prefix}_#{random_hex}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|