Skip to content

Commit

Permalink
Fix timestamp precision for Rails (#24)
Browse files Browse the repository at this point in the history
When using the Oj encoder in Rails you may inadvertently not get send sub-second timing on Date and Times.

This update changes the default Oj encoder to be in Rails mode that respects the `ActiveSupport::JSON::Encoding.time_precision` parameter if you are using Rails. If you have already set defaults for Oj those will not be changed.
  • Loading branch information
malomalo authored Apr 29, 2024
1 parent 90c545c commit cc3ea32
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 111 deletions.
28 changes: 10 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,26 @@ on:
branches: [ master ]

jobs:
sunstone:
name: TurboStremaer Test
turbostreamer:
name: CI
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby:
- '2.7'
- '3.0'
- '3.1'
- '3.2'
- '3.3'
encoder: [oj, wankel]
gemfile:
- gemfiles/rails_6_0.gemfile
- gemfiles/rails_6_1.gemfile
- gemfiles/rails_7_0.gemfile
- rails_6_0
- rails_6_1
- rails_7_0
- rails_7_1
allow_failures:
- false
include:
- ruby: ruby-head
gemfile: gemfiles/rails_7_0.gemfile
encoder: oj
allow_failures: true
- ruby: ruby-head
gemfile: gemfiles/rails_7_0.gemfile
encoder: wankel
allow_failures: true
env:
BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
BUNDLE_GEMFILE: "gemfiles/${{ matrix.gemfile }}.gemfile"
ALLOW_FAILURES: "${{ matrix.allow_failures }}"

steps:
Expand All @@ -45,7 +37,7 @@ jobs:
cd 'yajl-2.1.0' && ./configure && make && sudo make install
sudo ldconfig
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions Appraisals
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ appraise "rails_7_0" do
version = "~> 7.0.0"
gem "rails", version
end

appraise "rails_7_1" do
version = "~> 7.1.0"
gem "rails", version
end
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
[![Gem Version](http://img.shields.io/gem/v/turbostreamer.svg?style=flat-square)](http://badge.fury.io/rb/turbostreamer)
[![License](https://img.shields.io/github/license/malomalo/turbostreamer.svg?style=flat-square)](http://badge.fury.io/rb/turbostreamer)

TurboStreamer gives you a simple DSL for generating JSON that beats massaging giant
hash structures. This is particularly helpful when the generation process is
fraught with conditionals and loops.

TurboStreamer gives you a simple DSL like jBuilder for generating JSON that
streams directly to a String or IO object.

[Jbuilder](https://github.com/rails/jbuilder) builds a Hash as it renders the
template and once complete converts the Hash to JSON. TurboStreamer on the other
hand writes directly to the output as it is rendering the template. Because of
this some of the magic cannot be done and requires a little more verboseness.

Examples
--------
Because no time is spent creating a hash caching is also fast. No time is spent
marshaling and unmarshaling from the cache, instead the string is cached and
directly inserted into the stream skipping any unmarshaling.

## Examples

``` ruby
# app/views/message/show.json.streamer
Expand Down
7 changes: 7 additions & 0 deletions gemfiles/rails_7_1.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rails", "~> 7.1.0"

gemspec path: "../"
22 changes: 15 additions & 7 deletions lib/turbostreamer.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
require 'stringio'
require 'turbostreamer/key_formatter'
require 'turbostreamer/errors'

class TurboStreamer

autoload :Handler, 'turbostreamer/handler'
autoload :Template, 'turbostreamer/template'
autoload :KeyFormatter, 'turbostreamer/key_formatter'
autoload :Errors, 'turbostreamer/errors'

BLANK = ::Object.new

ENCODERS = {
Expand Down Expand Up @@ -228,18 +231,23 @@ def self.key_formatter=(formatter)
@@key_formatter = formatter
end

def self.set_default_encoder(mime, encoder, default_options={})
if encoder.is_a?(Symbol)
@@default_encoders[mime] = get_encoder(mime, encoder)
def self.set_default_encoder(mime, encoder, default_options=nil)
@@default_encoders[mime] = if encoder.is_a?(Symbol)
get_encoder(mime, encoder)
else
@@default_encoders[mime] = encoder
encoder
end
@@encoder_options[encoder] = default_options

@@encoder_options[encoder] = default_options if default_options
end

def self.set_default_encoder_options(encoder, options)
@@encoder_options[encoder] = options
end

def self.has_default_encoder_options?(encoder)
@@encoder_options.has_key?(encoder)
end

def self.get_encoder(mime, key)
require "turbostreamer/encoders/#{key}"
Expand Down
2 changes: 0 additions & 2 deletions lib/turbostreamer/dependency_tracker.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'turbostreamer'

dependency_tracker = false

begin
Expand Down
2 changes: 0 additions & 2 deletions lib/turbostreamer/errors.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


class TurboStreamer
module Errors
class MergeError < ::StandardError
Expand Down
9 changes: 3 additions & 6 deletions lib/turbostreamer/handler.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
require "turbostreamer"
require "active_support/core_ext"
# This module makes TurboStreamer work with Rails using the template handler API.

# This module makes TurboStreamer work with Rails using the template handler
# API.
class TurboStreamer
class Handler

Expand All @@ -12,11 +13,7 @@ def self.supports_streaming?
true
end

# TODO: setting source=nil is for rails 5.x compatability, once unsppored
# source can be a required param and
# `source = template.source if source.nil?` can be removed
def self.call(template, source=nil)
source = template.source if source.nil?
def self.call(template, source)
# this juggling is required to keep line numbers right in the error
%{__already_defined = defined?(json); json||=TurboStreamer::Template.new(self, output_buffer: output_buffer || ActionView::OutputBuffer.new); #{source}
json.target! unless (__already_defined && __already_defined != "method")}
Expand Down
48 changes: 22 additions & 26 deletions lib/turbostreamer/key_formatter.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
require 'active_support/core_ext/array'
class TurboStreamer::KeyFormatter
def initialize(*args)
@format = {}
@cache = {}

class TurboStreamer
class KeyFormatter
def initialize(*args)
@format = {}
@cache = {}

options = args.extract_options!
args.each do |name|
@format[name] = []
end
options.each do |name, paramaters|
@format[name] = paramaters
end
options = args.extract_options!
args.each do |name|
@format[name] = []
end

def initialize_copy(original)
@cache = {}
options.each do |name, paramaters|
@format[name] = paramaters
end
end

def initialize_copy(original)
@cache = {}
end

def format(key)
@cache[key] ||= @format.inject(key.to_s) do |result, args|
func, args = args
if ::Proc === func
func.call result, *args
else
result.send func, *args
end
def format(key)
@cache[key] ||= @format.inject(key.to_s) do |result, args|
func, args = args
if ::Proc === func
func.call result, *args
else
result.send func, *args
end
end
end
end
end
20 changes: 15 additions & 5 deletions lib/turbostreamer/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
require 'rails/railtie'
require 'turbostreamer/handler'
require 'turbostreamer/template'

require File.expand_path('../../../ext/actionview/buffer', __FILE__)
require File.expand_path('../../../ext/actionview/streaming_template_renderer', __FILE__)

class TurboStreamer
class Railtie < ::Rails::Railtie
initializer :turbostreamer do
ActiveSupport.on_load :action_view do
# Require turbostreamer in here so it's only loaded if needed
require 'turbostreamer'
require File.expand_path('../../../ext/actionview/buffer', __FILE__)
require File.expand_path('../../../ext/actionview/streaming_template_renderer', __FILE__)

# Register Turbostreamer with Rails
ActionView::Template.register_template_handler :streamer, TurboStreamer::Handler

# Setup the default for oj to be rails mode by default unless options
# have already been set
if TurboStreamer.default_encoder_for(:json).name == 'TurboStreamer::OjEncoder'
if !TurboStreamer.has_default_encoder_options?(:oj)
TurboStreamer.set_default_encoder_options(:oj, mode: :rails)
end
end

require 'turbostreamer/dependency_tracker'
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/turbostreamer/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
class TurboStreamer
VERSION = '1.10.0'
VERSION = '1.11.0'
end
15 changes: 5 additions & 10 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@
# installed gem
$LOAD_PATH << File.expand_path('../lib', __FILE__)

require "minitest/reporters"
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new

require "active_support"
require 'turbostreamer'
require 'turbostreamer/railtie'

require 'action_view'
require 'action_view/testing/resolvers'

require 'turbostreamer'
require 'turbostreamer/handler'
require 'turbostreamer/template'

require File.expand_path('../../ext/actionview/buffer', __FILE__)
require File.expand_path('../../ext/actionview/streaming_template_renderer', __FILE__)

require "active_support/testing/autorun"
require 'mocha/minitest'
require 'wankel'

if ENV["TSENCODER"]
TurboStreamer.set_default_encoder(:json, ENV["TSENCODER"].to_sym)
Expand All @@ -26,7 +21,7 @@
class ActiveSupport::TestCase

def jbuild(*args, &block)
::Wankel.parse(TurboStreamer.encode(*args, &block))
::JSON.parse(TurboStreamer.encode(*args, &block))
end

def assert_json(json, &block)
Expand Down
8 changes: 2 additions & 6 deletions test/turbo_streamer/options_test.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
require 'test_helper'

class TurboStreamer::OptionsTest < ActiveSupport::TestCase

setup do
@default_encoder = TurboStreamer.class_variable_get('@@default_encoders')[:json]
@default_options = TurboStreamer.class_variable_get('@@encoder_options').dup
end

teardown do
TurboStreamer.set_default_encoder(:json, @default_encoder, @default_options)
TurboStreamer.class_variable_set(:@@default_encoders, {})
TurboStreamer.class_variable_set(:@@encoder_options, Hash.new { |h, k| h[k] = {} })
end

test 'setting default options' do
Expand Down
Loading

0 comments on commit cc3ea32

Please sign in to comment.