active_support_cr
Version, currently 0.1.412 versions
- 0.1.10latestOct 22, 2026
- 0.1.9not indexedOct 22, 2026
- 0.1.8not indexedOct 22, 2026
- 0.1.7not indexedOct 22, 2026
- 0.1.6not indexedOct 22, 2026
- 0.1.5not indexedOct 22, 2026
- 0.1.4Oct 17, 2026
- 0.1.3not indexedOct 17, 2026
- 0.1.2not indexedOct 17, 2026
- 0.1.1not indexedOct 17, 2026
- 0.1.0not indexedOct 17, 2026
- master branchOct 12, 2026
github.com/OrelSokolov/active_support.cr
No description declared in shard.yml.
Installation
# Add this to your shard.yml
dependencies:
active_support_cr:
github: OrelSokolov/active_support.cr
version: ~> 0.1.4Then run:
shards installshard.yml
- Crystal
>= 1.21.0
Dependencies
Runtime Dependencies
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
active_support.cr
A Crystal port of Rails ActiveSupport 8.1.4 — core extensions
(blank?, Duration, date math, Inflector, deep hash ops), Concern,
delegate, Notifications, cache stores, I18n, number helpers, message
verification/encryption and time zones. Namespace ActiveSupport
mirrors the original 1:1; the goal is that Ruby snippets from the Rails
docs work unchanged. Estimated parity ~85%. Core extensions are
implemented by reopening Crystal stdlib types (adding methods is allowed;
overriding existing ones is not) and by macros in place of
define_method.
⚠️ EXPERIMENTAL FRAMEWORK, DO NOT USE IN PRODUCTION YET.
Installation
dependencies:
active_support_cr:
github: OrelSokolov/active_support.cr
shards install
Requires Crystal >= 1.21.0. pg and redis are needed only for the
optional adapters/* cache stores.
Usage
require "active_support_cr"
2.days.ago
%w[a b c].to_sentence # => "a, b, and c"
"post".pluralize # => "posts"
hash.deep_merge(other)
"42".present? # => true
Time.use_zone("Europe/Berlin") { Time.current }
Ruby-compat aliases
Ruby's attr_* macros live in the zero-dependency
attr_reader.cr shard and are
re-exported here (delegating to the idiomatic Crystal
getter/setter/property):
class Entry
attr_reader :body, :content_type # Ruby symbol style
attr_accessor hits : Int32 = 0 # typed declarations work too
end
Feature comparison vs activesupport 8.1.4
Core extensions
| Feature | Original (activesupport) | Port (active_support.cr) | Status |
|---|---|---|---|
Object: blank?, present?, presence, in?, presence_in, deep_dup, to_param | ✅ | Same | ✅ |
NilClass: blank?, present?, try | ✅ | Same | ✅ |
String: squish, truncate, truncate_bytes, at/from/to, first/last, starts_with?/ends_with?, exclude?, remove, indent, strip_heredoc | ✅ | Same | ✅ |
Inflector: pluralize, camelize, underscore, tableize, classify, humanize, titleize, parameterize, demodulize, foreign_key, ordinalize | ✅ | Same | ✅ |
Hash: symbolize_keys, deep_symbolize_keys, stringify_keys, except, slice, reverse_merge, with_defaults, deep_merge, deep_transform_keys/values, compact_blank, assert_valid_keys | ✅ | Same | ✅ |
Array: in_groups_of, in_groups, split, extract!, to_sentence, second..fifth, second_to_last, wrap, excluding, including | ✅ | Same | ✅ |
Enumerable: index_by, index_with, many?, exclude?, pluck, pick, sole, in_order_of, compact_blank | ✅ | Same | ✅ |
Int/Numeric: seconds..years, bytes..exabytes, multiple_of?, in_milliseconds (Duration mechanics) | ✅ | Duration is a fixed Time::Span (see deviations) | 🔶 |
Date/Time math: beginning/end_of_*, all_day/..., ago/since, advance, change, yesterday/tomorrow, next_week/prev_*, months_ago/..., next_occurring, past?/future?/today?, to_fs(:db/:short/:long/:iso8601) | ✅ | Same | ✅ |
constantize / String#constantize | ✅ | Not portable — dependent shards use compile-time registries | ❌ (equivalent: compile-time class registry) |
Time zones
| Feature | Original | Port | Status |
|---|---|---|---|
Time.zone, TimeWithZone, in_time_zone, utc/localtime, formatted_offset | ✅ | TimeZone built on Time::Location; TWZ arithmetic, <=>/== by UTC instant, zoned strftime, to_fs | ✅ |
Per-user timezone: Time.use_zone(zone, &) | ✅ | Fiber-local override registry + ensure-restore | ✅ |
Time.current, TimeZone#now, I18n.l with TWZ | ✅ | Same | ✅ |
| DST-aware calendar Duration arithmetic across TWZ | ✅ | Duration is a fixed span — twz + 1.day always shifts 24 h | ❌ (equivalent: shift via Time#shift calendar math) |
Infrastructure
| Feature | Original | Port | Status |
|---|---|---|---|
ActiveSupport::Concern (included do / class_methods do) | ✅ | Crystal included hook + macros | ✅ |
attr_reader / attr_writer / attr_accessor | ✅ | Re-exported from the attr_reader.cr shard; Ruby symbol args (:foo) and typed declarations accepted | ✅ |
delegate :x, to: :y, prefix:, allow_nil: / delegate_missing_to | ✅ | Macros | ✅ |
mattr_accessor/cattr_accessor, class_attribute, thread_mattr_* | ✅ | Macros over @@/fiber-locals | ✅ |
ActiveSupport::Notifications (instrument/subscribe/publish) | ✅ | Same | ✅ |
ActiveSupport::Cache: MemoryStore, NullStore, FileStore; fetch/read/write/exist?/delete, expires_in:, race_condition_ttl: | ✅ | Same Store API | ✅ |
RedisCacheStore | ✅ | adapters/redis_cache.cr (SET/GET EX, INCRBY counters, namespace); stores raw strings — INCR on a write-stored integer succeeds (Rails wraps values in a coder and returns nil) | 🔶 |
SolidCache (PG table) | separate gem | adapters/pg_cache.cr: solid_cache_entries(key, value, expires_at) + atomic SQL counters + delete_expired | 🔶 |
I18n: t(key, scope:, default:, count:), yml locales, pluralization, l, available_locales, fallbacks | ✅ | Same | ✅ |
NumberHelper family | ✅ | Same (locale data) | ✅ |
MessageVerifier / MessageEncryptor (signed/encrypted cookies) | ✅ | SHA1/SHA256 digests, KeyGenerator-derived keys, legacy {"_rails":...} envelope read/write (wire format of actionpack 8.1 cookies) | ✅ |
SecureRandom.base58/base36, Digest::UUID | ✅ | Same | ✅ |
ActiveSupport::CurrentAttributes | ✅ | class Current < AS::CurrentAttributes, typed attribute user: User? (nilable), class-level accessors, reset/reset_all; reset wired into request dispatch by railties.cr | 🔶 (process-global class vars, not fiber-isolated; no instance/set{}/callbacks API) |
TaggedLogging, Deprecation, BacktraceCleaner | ✅ | Same | ✅ |
HashWithIndifferentAccess | ✅ | Approximation used by params: Symbol|String keys | 🔶 |
Deviations & Crystal-specific notes
- Core extensions reopen stdlib types (
struct Int,class String); overriding existing stdlib methods is forbidden, so those keep Crystal semantics. Durationis a fixedTime::Span: calendarmonths/yearshave variable length, and TWZ arithmetic does not cross DST boundaries the way Rails durations do.- The fiber-local zone registry is not cleaned up if a fiber dies inside
a
use_zoneblock;Time.zonewithout a fiber override is process-global (like Rails'Time.zone=). - FileStore writes timestamps with sub-second precision (expiry < 1 s).
- Cache stores keep raw string values (see RedisCacheStore row).
Testing
crystal spec
Suite: 323/323. Oracle specs (oracle/cases.rb → expected.yml,
including 19 tz_* timezone cases) are recorded with the real
activesupport 8.1.4 and replayed unchanged; cache stores are additionally
verified against a real redis-server (oracle/cache_stores_cases.rb)
and a shared contract spec runs across all four stores.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.1.4- Tagged
- Oct 17, 2026
- Commit
793134515299- Crystal
>= 1.21.0- Indexed
- yes
Dependents
- action_pack_cr
- turbo_cr
- action_view_cr
- once_campfire_crails
- active_storage_cr
- railties_cr
- active_record_cr
- importmap_cr
- action_cable_cr
- active_job_cr
- rails_settings_cached_cr
- active_model_cr
and 7 more
Repository
github.com/OrelSokolov/active_support.cr
Metadata
- Created
- Oct 13, 2026
- Updated
- Oct 23, 2026
- Synced
- Oct 23, 2026
- Versions
- 12