alignment_audit
Legacy Migrations
Outstanding
-
Migrate away from
schema/folder structure as mentioned in ruby_frontend.md
alignment_audit
-
Symbol Sets
-
line::Set, block::Set, scrollbar::Set
-
Layout
-
<s>Constraint batch constructors (6)</s> β DONE
-
Layout margin, spacing (2)
-
Style
-
sub_modifier, underline_color (2)
-
Text
-
Span methods (4)
-
Line methods (6)
alignment_audit_granular_level
v0.7.0 Alignment Audit (Parameter-Level)
This document audits alignment between RatatuiRuby v0.7.0 and the upstream Ratatui/Crossterm Rust libraries at the parameter and enum value level. Only gaps are listed.
MISSING β Layout Module
Layout::Constraint β Batch Constructors β
DONE
All 6 batch constructors implemented in v0.8.0: - from_lengths, from_percentages, from_mins, from_maxes, from_fills, from_ratios
Layout::Layout β Missing Parameters
| Missing Parameter | Ratatui Type | Notes |
|---|---|---|
margin |
Margin { horizontal, vertical } |
Edge margins |
spacing |
u16 |
Gap between segments |
MISSING β Style Module
Style::Style β Missing Parameters/Methods
MISSING β Text Module
Text::Span β Missing Methods
| Missing Method | Signature | Notes |
|---|---|---|
width |
span.width β Integer
|
Display width in terminal cells |
raw |
Span.raw(content) β Span
|
Constructor without style (alias for new(content:)) |
patch_style |
span.patch_style(style) β Span
|
Merge style on top of existing |
reset_style |
span.reset_style β Span
|
Clear style |
Text::Line β Missing Methods
| Missing Method | Signature | Notes |
|---|---|---|
left_aligned |
line.left_aligned β Line
|
Fluent setter for :left alignment |
centered |
line.centered β Line
|
Fluent setter for :center alignment |
right_aligned |
line.right_aligned β Line
|
Fluent setter for :right alignment |
push_span |
line.push_span(span) β Line
|
Append span |
patch_style |
line.patch_style(style) β Line
|
Merge style on all spans |
reset_style |
line.reset_style β Line
|
Clear style on all spans |
Widgets::Table β Missing Row Methods (via Widgets::Row)
| Missing | Ratatui API | Notes |
|---|---|---|
enable_strikethrough |
row.enable_strikethrough() |
Enable strikethrough on row |
Widget Computation Methods β Missing
These are public &self methods on upstream widgets that compute/query values without rendering.
| Widget | Missing Method | Ratatui API | Notes | |βββ|βββββ-|ββββ-|ββ-|
| List | len | list.len() β usize | Number of items in the list | | List | is_empty | list.is_empty() β bool | True if no items (empty? in Ruby) | | Canvas | get_point | canvas.get_point(x, y) β Option<(usize, usize)> | Map coordinates to grid cell |
[!NOTE]
Paragraph#line_count(width)andParagraph#line_widthARE exposed βTabs#widthIS exposed β
State Navigation Methods β Missing
TableState has navigation helpers that are not exposed.
| State Class | Missing Method | Ratatui API | Notes |
|---|---|---|---|
TableState |
selected_cell |
state.selected_cell() |
Get (row, col) tuple |
TableState |
with_selected_cell |
state.with_selected_cell((r,c)) |
Builder pattern |
TableState |
select_next_column |
state.select_next_column() |
Navigate columns |
TableState |
select_previous_column |
state.select_previous_column() |
Navigate columns |
TableState |
select_first_column |
state.select_first_column() |
Jump to first column |
TableState |
select_last_column |
state.select_last_column() |
Jump to last column |
Layout Module β Additional Missing
| Module | Missing | Ratatui API | Notes |
|---|---|---|---|
Rect |
as_position |
rect.as_position() β Position
|
Convert to Position |
Rect |
as_size |
rect.as_size() β Size
|
Convert to Size |
Rect |
outer |
rect.outer(margin) β Rect
|
Expand by margin (inverse of inner) |
Rect |
resize |
rect.resize(size) β Rect
|
Change dimensions, preserve position |
Rect |
centered_horizontally |
rect.centered_horizontally(constraint) β Rect
|
Center horizontally via Layout |
Rect |
centered_vertically |
rect.centered_vertically(constraint) β Rect
|
Center vertically via Layout |
Rect |
centered |
rect.centered(h, v) β Rect
|
Center both axes |
Constraint |
apply |
constraint.apply(length) β u16
|
Compute constrained size |
Layout |
split_with_spacers |
layout.split_with_spacers(area) |
Returns segments AND spacers |
Color β Missing Constructors
| Missing | Ratatui API | Notes |
|---|---|---|
from_u32 |
Color::from_u32(0xRRGGBB) |
Construct from hex integer |
from_hsl |
Color::from_hsl(hsl) |
Construct from HSL (requires palette feature) |
from_hsluv |
Color::from_hsluv(hsluv) |
Construct from HSLuv (requires palette feature) |
Buffer β Missing Query Methods
| Missing Method | Ratatui API | Notes |
|---|---|---|
content |
buffer.content() β &[Cell]
|
Get all cells as slice |
get |
buffer.get(x, y) β &Cell
|
Get cell at position |
index_of |
buffer.index_of(x, y) β usize
|
Position to linear index |
pos_of |
buffer.pos_of(i) β (u16, u16)
|
Linear index to position |
alignment_audit_high_level
v0.7.0 Alignment Audit
This document audits strict alignment between RatatuiRuby v0.7.0 and the upstream Ratatui/Crossterm Rust libraries. The audit covers modules, classes, static methods, and constructor arguments as specified in the Ruby Frontend Design.
[!NOTE] The TUI facade API is explicitly excluded from this audit. It provides ergonomic shortcuts that intentionally diverge from Ratatui naming.
β
| margin | Margin | β Missing | Gap | | spacing | u16 | β Missing | Gap |
Verdict: Core layout aligned. Margin and spacing are gaps.
Gaps Analysis: MISSING vs MISALIGNED
[!IMPORTANT] MISSING = Can be added as new features without breaking backwards compatibility.
MISALIGNED = Requires breaking changes before v1.0.0 to fix API shape.
MISSING Features (Additive, Backwards-Compatible) β
These are gaps that can be filled in future minor releases without breaking existing code:
| Component | Missing Feature | Notes |
|---|---|---|
Constraint |
from_lengths(), from_percentages(), etc. |
New class methods |
Layout |
margin, spacing
|
New optional constructor args |
Style |
sub_modifier, underline_color
|
New optional constructor args |
Span |
width() instance method |
New instance method |
Span |
raw() constructor |
New class method (alias for new) |
Line |
left_aligned(), centered(), right_aligned()
|
New instance methods (fluent) |
alignment_audit_symbol_sets
Audit of symbol set alignment between Ratatuiβs symbols:: module and RatatuiRuby.
[!IMPORTANT] MISSING = Can be added as new features, backwards-compatible. MISALIGNED = Requires breaking changes before v1.0.0.
| scrollbar::Set | 4 predefined sets | β Not exposed | MISSING | | shade constants | 5 constants | β Not exposed | MISSING |
MISSING β Marker Enum
Marker::HalfBlock
| Marker::HalfBlock | β Not exposed | MISSING |
Impact: Users cannot use the HalfBlock marker type, which provides double-resolution square pixels using β, β, and β characters.
MISSING β Line Set Customization
Ratatui provides symbols::line::Set with predefined sets: - NORMAL β Standard box-drawing characters - ROUNDED β Rounded corners - DOUBLE β Double-line characters - THICK β Thick line characters
Ruby Status: Not directly exposed. Users cannot customize line symbols for widgets that use them internally.
MISSING β Bar Set Customization
Ratatui provides symbols::bar::Set with predefined sets: - THREE_LEVELS β 3 distinct fill levels - NINE_LEVELS β 9 distinct fill levels (default)
Ruby Status: Not exposed. Used internally by widgets like Sparkline but not configurable.
MISSING β Block Set Customization
Ratatui provides symbols::block::Set with predefined sets: - THREE_LEVELS β 3 distinct fill levels - NINE_LEVELS β 9 distinct fill levels (default)
Ruby Status: Not exposed. Used internally by Gauge widget but not configurable.
MISSING β Scrollbar Set Customization
Ratatui provides symbols::scrollbar::Set with predefined sets: - DOUBLE_VERTICAL β Double-line vertical scrollbar - DOUBLE_HORIZONTAL β Double-line horizontal scrollbar - VERTICAL β Single-line vertical scrollbar - HORIZONTAL β Single-line horizontal scrollbar
Ruby Status: Not exposed. Scrollbar widget not currently implemented in RatatuiRuby.
MISSING β Shade Constants
Ratatui provides symbols::shade constants: - EMPTY β (space) - LIGHT β β - MEDIUM β β - DARK β β
Recommendations
| Priority | Item | Notes |
|---|---|---|
| Low | Add :half_block marker |
Single symbol addition |
| Low | Expose line::Set customization |
For LineGauge widget |
| Low | Expose bar::Set customization |
For Sparkline widget |
| Low | Expose block::Set customization |
For Gauge widget |
| Medium | Implement Scrollbar widget | Would include scrollbar::Set |
All missing items are additive and do not require breaking changes.
alignment_audit_tui_final
TUI API Alignment Audit
This document audits the RatatuiRuby::TUI facade API for method and parameter naming, with a focus on Developer Experience (DX) before the v1.0.0 release.
Design Philosophy
The TUI API follows a βMullet Architectureβ: structured namespaces in the library, flat ergonomic DSL for users.
Guiding Principles:
-
Terseness β Fewer keystrokes for common operations
-
DWIM β Do What I Mean; intuitive defaults
-
TIMTOWTDI β Multiple valid ways to express the same thing
-
Big Tent β Aliases for CSS/frontend developers, Ratatui natives, and Ruby purists
-
Two Levels Max β
tui.thingandtui.scope_thing, nevertui.scope.thing, nevertui.ascope_bscope_thing
Breaking Changes: Pre-1.0 with few external users. Rename aggressively for DX. Document in CHANGELOG.
Method Naming Recommendations
Pattern: Base Methods + Aliases + Dispatchers
Base methods align with Ratatuiβs module API names. Aliases provide ergonomic shortcuts.
-
Base method (Ratatui-aligned):
tui.shape_circle(...)β matchesWidgets::Shape::Circle -
Aliases (ergonomic):
tui.circle(...),tui.circle_shape(...) -
Dispatcher:
tui.shape(:circle, ...)β errors helpfully on missing type
This pattern applies to: shapes, constraints, text elements.
Canvas Shapes
| Base Method | Add Aliases | Dispatcher |
|---|---|---|
shape_circle |
circle, circle_shape
|
shape(:circle, ...) |
shape_line |
line_shape (not bare line β conflicts with Text::Line)
|
shape(:line, ...) |
shape_point |
point, point_shape
|
shape(:point, ...) |
shape_rectangle |
rectangle, rectangle_shape
|
shape(:rectangle, ...) |
shape_map |
map, map_shape
|
shape(:map, ...) |
shape_label |
label, label_shape
|
shape(:label, ...) |
Dispatcher signature:
def shape(type, **kwargs) case type when :circle then shape_circle(**kwargs) when :line then shape_line(**kwargs) # ... else raise ArgumentError, "Unknown shape type: #{type.inspect}. " \ "Valid types: :circle, :line, :point, :rectangle, :map, :label" end end
Layout Constraints
Ratatuiβs constraints map well to CSS layout concepts. Offer aliases for both communities:
| Base Method | Add Aliases (CSS-Friendly) | Dispatcher |
|---|---|---|
constraint_length(n) |
fixed(n), length(n)
|
constraint(:length, n) |
constraint_percentage(n) |
percent(n), percentage(n)
|
constraint(:percentage, n) |
constraint_min(n) |
min(n), min_content(n)
|
constraint(:min, n) |
constraint_max(n) |
max(n), max_content(n)
|
constraint(:max, n) |
constraint_fill(n) |
fill(n), flex(n), fr(n)
|
constraint(:fill, n) |
constraint_ratio(a,b) |
ratio(a,b), aspect(a,b)
|
constraint(:ratio, a, b) |
CSS Flexbox/Grid parallels: - fill(1) β CSS flex: 1 or 1fr - fixed(100) β CSS width: 100px - min(50) β CSS min-width: 50px - percent(25) β CSS width: 25%
Dispatcher signature:
def constraint(type, *args) case type when :length, :fixed then constraint_length(*args) when :percentage, :percent then constraint_percentage(*args) when :min then constraint_min(*args) when :max then constraint_max(*args) when :fill, :flex, :fr then constraint_fill(*args) when :ratio, :aspect then constraint_ratio(*args) else raise ArgumentError, "Unknown constraint type: #{type.inspect}. " \ "Valid types: :length, :percentage, :min, :max, :fill, :ratio" end end
Layout Operations
| Current | Add Alias | Rationale |
|---|---|---|
layout_split |
split |
52 usages in examples; clear in context |
Text Factories
| Current | Status | Notes |
|---|---|---|
text_span |
β Has alias span
|
|
text_line |
β Has alias line
|
|
text_width |
Keep as-is | Distinct from length (constraint) |
Add dispatcher:
def text(type, **kwargs) case type when :span then text_span(**kwargs) when :line then text_line(**kwargs) else raise ArgumentError, "Unknown text type: #{type.inspect}. Valid types: :span, :line" end end
Widget Factories
| Current | Add Alias | Rationale |
|---|---|---|
| list_item | item |
Clear in list context |
table_row |
Keep alongside row
|
DWIM β both valid mental models |
table_cell |
Keep as-is |
cell means Buffer::Cell
|
bar_chart_bar |
Keep alongside bar
|
DWIM β donβt deprecate |
bar_chart_bar_group |
Keep alongside bar_group | DWIM β donβt deprecate |
Add dispatcher:
def widget(type, **kwargs) case type when :block then block(**kwargs) when :paragraph then paragraph(**kwargs) when :list then list(**kwargs) when :table then table(**kwargs) # ... all widgets else raise ArgumentError, "Unknown widget type: #{type.inspect}." end end
State Factories
| Current | Status |
|---|---|
| list_state | Keep as-is |
| table_state | Keep as-is |
| scrollbar_state | Keep as-is |
Add dispatcher:
def state(type, **kwargs) case type when :list then list_state(**kwargs) when :table then table_state(**kwargs) when :scrollbar then scrollbar_state(**kwargs) else raise ArgumentError, "Unknown state type: #{type.inspect}." end end
Parameter Names
All current parameter names are well-chosen. No changes recommended.
| Widget | Parameter | Status |
|---|---|---|
List |
selected_index, highlight_style, etc. |
β |
Table |
row_highlight_style, selected_row, etc. |
β |
Scrollbar |
content_length, position, etc. |
β |
| All |
block, style, offset
|
β Consistent |
Summary of Changes
High Priority (Immediate DX Wins)
-
Add
itemalias for list_item -
Add terse shape aliases:
circle,point,rectangle,map,label
Medium Priority (Pattern Completion)
-
Add dispatcher methods:
shape(type, ...),constraint(type, ...),text(type, ...),widget(type, ...),state(type, ...) -
Add bidirectional shape aliases:
circle_shape,point_shape, etc.
Not Changing
-
Donβt deprecate verbose forms (
table_row,bar_chart_bar, etc.) β DWIM -
Donβt rename parameters β already optimal
-
Donβt add third level aliases (
tui.widgets.paragraph) β two levels max
Implementation Checklist
-
[ ] Add
itemalias toWidgetFactories -
[ ] Add terse shape aliases to
CanvasFactories -
[ ] Add
shape(type, ...)dispatcher -
[ ] Add
constraint(type, ...)dispatcher -
[ ] Add bidirectional shape aliases (
*_shape) -
[ ] Add
text(type, ...),widget(type, ...),state(type, ...)dispatchers -
[ ] Update RBS signatures for all new methods
-
[ ] Update RDoc for all new methods
-
[ ] Update CHANGELOG.md
Breaking Changes Analysis
If all recommendations in this audit are adopted, none constitute breaking changes under semver.
| Recommendation | Breaking? | Rationale |
|---|---|---|
Add item alias |
No | Additive; list_item unchanged |
Add terse shape aliases (circle, etc.) |
No | Additive; shape_* methods unchanged |
Add bidirectional aliases (*_shape) |
No | Additive; does not remove existing forms |
| Add dispatcher methods | No | Additive; new methods only |
Keep verbose forms (table_row, etc.) |
No | No removal or rename |
Conclusion: This audit recommends only additive changes. All existing code will continue to work unchanged.
[!NOTE] If we later decide to remove verbose forms like
bar_chart_barorbar_chart_bar_group, that would be a breaking change requiring a major version bump. This audit explicitly recommends keeping them (DWIM philosophy).
dwim_dx
dwim_dx
Problem Statement
Rubyβs philosophy of βDo What I Meanβ (DWIM) and human-centric design should extend to ratatui_rubyβs API. Currently, app developers encounter friction points that force them to remember non-obvious conventions, use overly verbose code, or pattern-match when simple predicates would suffice.
This proposal identifies DX issues across the widget API and suggests improvements that maintain backward compatibility while providing ergonomic alternatives.
DX Issues Identified
1. Confusing Event Method Names
Current problem: event.char doesnβt exist, but event.code returns things like "enter", "ctrl", not just characters.
What users expect: - event.char should return the printable character (matching the name) - event.ctrl_c?, event.enter?, etc. should work for all key combinations - event.key?, event.mouse? predicates exist but only for broad categories
Solution implemented: Added char method and dynamic predicates via method_missing. See lib/ratatui_ruby/event/key.rb.
2. Dual Parameter APIs Without Predicates
Current problem: Widgets accept both forms but no convenience methods to query the state:
### Both work, but which one does the widget store? gauge1 = Gauge.new(ratio: 0.75) gauge2 = Gauge.new(percent: 75) gauge1.ratio # Works gauge1.percent # Does NOT exist
Similarly with List and Table:
list.selected_index = 2 # Works list.selected? # Does NOT exist list.is_selected? # Does NOT exist
Affected widgets: - Gauge (ratio vs percent) - LineGauge (ratio vs percent) - List (selected_index with no query methods) - Table (selected_row and selected_column with no query methods)
Suggested solutions:
For Gauge and LineGauge:
### Add convenience predicates gauge.percent # => 75 (coerced from ratio internally) gauge.percent = 50 # => Updates ratio to 0.5 ### Or provide explicit accessors gauge.as_percent # => 75 gauge.as_ratio # => 0.75
For List and Table:
list.selected? # => true if selected_index is not nil list.selection # => 2 (alias for selected_index) list.selected_item # => "Item 3" table.selected_row? # => true if selected_row is not nil table.selected_cell? # => true if both row and column selected
4. Inconsistent Style APIs
Current problem: Different widgets accept styles differently:
### Table accepts both table = Table.new(style: Style.new(fg: :blue)) table = Table.new(style: { fg: :blue }) # Hash shorthand ### But Paragraph doesn't paragraph = Paragraph.new(text: "hi", style: Style.new(fg: :blue)) paragraph = Paragraph.new(text: "hi", style: { fg: :blue }) # Works but undocumented ### And Gauge has separate properties gauge = Gauge.new(style: Style.new(fg: :blue), gauge_style: Style.new(fg: :green))
Suggested solution: Standardize style handling across all widgets:
-
All widgets should accept
Styleobjects andHashshorthand -
Document this clearly in each widget
-
Add a convenience constructor:
class Style def self.with(fg: nil, bg: nil, modifiers: []) Style.new(fg: fg, bg: bg, modifiers: modifiers) end end ### Cleaner than always spelling out keyword args paragraph = Paragraph.new(text: "hi", style: Style.with(fg: :blue))
6. Magic Numeric Coercions
Current problem: Widgets accept Numeric but silently coerce:
### These all work, but behavior is undocumented list = List.new(selected_index: "2") # Coerced to 2 list = List.new(selected_index: 2.7) # Coerced to 2 list = List.new(selected_index: 2.0) # Coerced to 2 gauge = Gauge.new(percent: 150) # Should clamp? gauge = Gauge.new(ratio: 1.5) # Should clamp?
Suggested solution:
-
Document coercion rules explicitly in RDoc
-
Add validation and raise on invalid inputs:
def initialize(percent: nil, ...) if percent raise ArgumentError, "percent must be 0..100, got #{percent}" unless percent.between?(0, 100) ratio = Float(percent) / 100.0 end end
-
Provide clear error messages:
gauge = Gauge.new(percent: 150) ### => ArgumentError: percent must be between 0 and 100 (got 150)
Implementation Strategy
Phase 3: Style Consistency
-
[ ] Standardize
Hashshorthand support across all widgets -
[ ] Add
Style.with(fg:, bg:, modifiers:)convenience constructor -
[ ] Update
.rbsfiles to reflect HashStyle support -
[ ] Document in style guide
Phase 4: Numeric Coercion Validation
-
[ ] Add validation to
Gauge,LineGauge,List,Table -
[ ] Raise
ArgumentErroron out-of-range values -
[ ] Provide clear error messages
-
[ ] Update tests
Phase 5: Convenience Accessors
-
[ ] Add
percenttoGaugeandLineGauge -
[ ] Add
selectionalias toListandTable -
[ ] Add
selected_itemtoList -
[ ] Tests and documentation
Example: Before and After
Before (Confusing)
class GameApp def initialize @menu = List.new( items: ["Start Game", "Load Game", "Options", "Quit"], selected_index: 0, highlight_spacing: :when_selected, # What's valid here? direction: :top_to_bottom ) end def handle_input(event) case event when :ctrl_c exit when :up if @menu.selected_index && @menu.selected_index > 0 @menu = @menu.with(selected_index: @menu.selected_index - 1) end end end def render(tui) tui.draw(@menu) end end
After (DWIM)
class GameApp def initialize @menu = List.new( items: ["Start Game", "Load Game", "Options", "Quit"], selected_index: 0, highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED, # IDE autocomplete! direction: List::DIRECTION_TOP_TO_BOTTOM ) end def handle_input(event) return if event.ctrl_c? # Dynamic predicate! if event.up? move_menu_up if @menu.selected? # State predicate! end end def move_menu_up index = @menu.selected_index return if index == 0 @menu = @menu.with(selected_index: index - 1) end def render(tui) tui.draw(@menu) end end
Migration Path
All changes are backward compatible (additive): - Existing code using symbols continues to work - New constants coexist with symbols - New predicates donβt change existing behavior - New methods are additions, not replacements
Apps can migrate at their own pace:
### Old style still works list = List.new(highlight_spacing: :when_selected) ### New style also works list = List.new(highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED) ### Mix and match if list.selected? # New predicate puts list.selected_index # Old accessor end
Metrics for Success
-
Discoverability: New developers can find valid options via IDE autocomplete
-
Clarity: Code self-documents valid states and modes
-
Type safety: Constants and predicates provide type checking
-
Error feedback: Invalid inputs raise with helpful messages
-
Backward compatibility: Zero breaking changes, all existing code works
Related Issues
-
AGENTS.md requirement: All examples must have tests verifying behavior
-
Example improvements: Apply constants and predicates to all example code
-
Documentation: Update style guide with DWIM principles
examples_audit
examples_audit
P1: Moderate (Quality)
-
{Add RDoc Cross-Links}[#1-add-rdoc-cross-links-examplesβaliases] (Documentation discoverability)
-
Link library classes/methods to examples
-
Link DWIM/TIMTOWTDI aliases
-
Create consistent pattern across public APIs
p2_moderate
Priority 2: Moderate (Quality Gates)
These are v1.0.0 quality improvements that refine the example suite after P0 is complete. Not blocking, but recommended for maintainability and API consistency.
1. Add RDoc Cross-Links (Examples & Aliases)
Status: Important for API discoverability β Documentation should link library and examples
RDoc should cross-link between: - Library classes/methods β Examples that use them (See also: examples/widget_foo) - Primary methods β DWIM/TIMTOWTDI aliases (See also: tui.foo_bar as alias for tui.foo(:bar))
Current Practice
Done for: - RatatuiRuby::Frame#set_cursor_position β RatatuiRuby::Cursor (cross-linking) - Limited elsewhere
Gaps
-
Most widget classes have no βSee also: example_foo_demoβ links
-
Aliases/TIMTOWTDI variants are not documented as such
-
Users canβt easily find examples for a given class/method
Action
-
Add
# See also: examples/widget_foo/app.rbto class/method RDoc -
Link DWIM methods to TIMTOWTDI variants:
# Also available as: tui.constraint_length (DWIM) vs tui.constraint(:length) (TIMTOWTDI) -
Create consistent pattern across all public APIs in
lib/ratatui_ruby/
Example Pattern
#### Renders text with styling. # #### See also: examples/widget_paragraph/app.rb (basic paragraph rendering) class Paragraph < Data.define(...) # ... end #### DWIM version of constraint creation #### Also available as: constraint(type, value) for explicit control def constraint_length(length) constraint(:length, length) end