Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
name: Ruby ${{ matrix.ruby }}
strategy:
fail-fast: false
matrix:
ruby:
- "3.2"
- "3.3"
- "3.4"
- "4.0"
- "ruby-head"

steps:
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ Gemfile.lock

# MacOS
.DS_Store

# Dummy app artifacts
spec/dummy/log/
spec/dummy/tmp/

# Claude
.claude/settings.local.json
9 changes: 7 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2025-11-17 23:28:22 UTC using RuboCop version 1.81.7.
# on 2026-01-25 10:40:25 UTC using RuboCop version 1.81.7.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 2
# Offense count: 1
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 12

# Offense count: 6
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 14
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Agents

## Style

- Always use Ruby keyword argument shorthand (e.g., `super(**kwargs, json:)` not `super(**kwargs, json: json)`).
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ gemspec

gem "rake", "~> 13.0"

gem "rails", ">= 7.2"

gem "rspec", "~> 3.0"
gem "rspec-rails", "~> 7.0"

gem "rubocop", "~> 1.21"
gem "rubocop-rspec", require: false
Expand All @@ -18,5 +21,7 @@ gem "undercover"

gem "pry"

gem "sqlite3"

gem "gem-release", require: false
gem "solargraph", require: false
23 changes: 19 additions & 4 deletions lib/transmutation/serialization/rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ module Transmutation
module Serialization
module Rendering
# Serializes the value of the `json` parameter before calling the existing render method.
def render(json: nil, serialize: true, namespace: nil, serializer: nil, max_depth: 1, **args)
return super(**args) unless json
return super(**args, json:) unless serialize
#
# Handles both Rails-style hash arguments (e.g., from send_data) and keyword arguments.
def render(options = nil, **kwargs)
# Handle Rails-style positional hash argument (e.g., from send_data calling render(body: data))
if options.is_a?(Hash)
return super(options) unless options.key?(:json)

super(**args, json: serialize(json, namespace:, serializer:, max_depth:))
kwargs = options.merge(kwargs)
end

json = kwargs.delete(:json)
should_serialize = kwargs.key?(:serialize) ? kwargs.delete(:serialize) : true
namespace = kwargs.delete(:namespace)
serializer = kwargs.delete(:serializer)
max_depth = kwargs.delete(:max_depth) || Transmutation.max_depth

return super(**kwargs) unless json
return super(**kwargs, json:) unless should_serialize

super(**kwargs, json: serialize(json, namespace:, serializer:, max_depth:))
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Api
class ApplicationController < BaseController
class ApplicationController < ::ApplicationController
include Transmutation::Serialization
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ module Api
module V1
class HealthController < Api::ApplicationController
def index
render(json: { ok: true })
render json: { ok: true }
end

def download
send_data("binary data content", filename: "report.txt", type: "text/plain")
send_data "binary data content", filename: "report.txt", type: "text/plain"
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ module V1
class PostsController < Api::ApplicationController
def index
posts = Post.all

render json: posts
end

def show(id)
post = Post.find(id)

def show
post = Post.find(params[:id])
render json: post, namespace: "Detailed"
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
module Api
module V1
class ProductsController < Api::ApplicationController
def show(id)
post = Product.find(id)

render json: post
def show
product = Product.find(params[:id])
render json: product
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ module V1
class UsersController < Api::ApplicationController
def index
users = User.all

render json: users
end

def show(id)
user = User.find(id)

def show
user = User.find(params[:id])
render json: user, serializer: "Detailed::UserSerializer"
end
end
Expand Down
4 changes: 4 additions & 0 deletions spec/dummy/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class ApplicationController < ActionController::API
end
5 changes: 5 additions & 0 deletions spec/dummy/app/models/comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class Comment < ActiveRecord::Base
belongs_to :user, optional: true
end
File renamed without changes.
5 changes: 5 additions & 0 deletions spec/dummy/app/models/post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class Post < ActiveRecord::Base
belongs_to :user, optional: true
end
7 changes: 7 additions & 0 deletions spec/dummy/app/models/product.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Product < ActiveRecord::Base
def price
Money.new(price_subunit, price_currency)
end
end
6 changes: 6 additions & 0 deletions spec/dummy/app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
24 changes: 24 additions & 0 deletions spec/dummy/config/application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

require_relative "boot"

require "rails"
require "active_record/railtie"
require "action_controller/railtie"

Bundler.require(*Rails.groups)

require "transmutation"

module Dummy
class Application < Rails::Application
config.root = File.expand_path("..", __dir__)

config.load_defaults Rails::VERSION::STRING.to_f

config.autoload_paths << config.root.join("app/serializers")

config.api_only = true
config.eager_load = false
end
end
6 changes: 6 additions & 0 deletions spec/dummy/config/boot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)

require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
3 changes: 3 additions & 0 deletions spec/dummy/config/database.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test:
adapter: sqlite3
database: ":memory:"
5 changes: 5 additions & 0 deletions spec/dummy/config/environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

require_relative "application"

Rails.application.initialize!
9 changes: 9 additions & 0 deletions spec/dummy/config/environments/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

Rails.application.configure do
config.eager_load = false
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_controller.allow_forgery_protection = false
config.active_support.deprecation = :stderr
end
16 changes: 16 additions & 0 deletions spec/dummy/config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :users, only: %i[index show]
resources :posts, only: %i[index show]
resources :products, only: [:show]
resources :health, only: [:index] do
collection do
get :download
end
end
end
end
end
27 changes: 27 additions & 0 deletions spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

ActiveRecord::Schema.define(version: 0) do
create_table :users, force: true do |t|
t.string :first_name, null: false
t.string :last_name, null: false
end

create_table :posts, force: true do |t|
t.string :title, null: false
t.text :body, null: false
t.references :user, foreign_key: true
t.datetime :published_at
end

create_table :comments, force: true do |t|
t.text :body, null: false
t.references :user, foreign_key: true
end

create_table :products, force: true do |t|
t.string :name, null: false
t.string :description
t.integer :price_subunit, null: false
t.string :price_currency, null: false
end
end
14 changes: 14 additions & 0 deletions spec/dummy/test/fixtures/comments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
first_comment:
id: 1
body: "First!"
user_id: 1

second_comment:
id: 2
body: "Second!"
user_id: 2

third_comment:
id: 3
body: "Third!"
user_id: 1
20 changes: 20 additions & 0 deletions spec/dummy/test/fixtures/posts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
first_post:
id: 1
title: First post
body: "First!"
user_id: 1
published_at: 2025-01-01 00:00:00

second_post:
id: 2
title: How does this work?
body: body
user_id: 2
published_at: 2025-01-01 00:00:00

third_post:
id: 3
title: "Second post!?"
body: "Nope..."
user_id: 1
published_at: null
6 changes: 6 additions & 0 deletions spec/dummy/test/fixtures/products.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
shoes:
id: 1
name: Shoes
description: A pair of shoes
price_subunit: 1000
price_currency: GBP
19 changes: 19 additions & 0 deletions spec/dummy/test/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
john:
id: 1
first_name: John
last_name: Doe

jane:
id: 2
first_name: Jane
last_name: Doe

adam:
id: 3
first_name: Adam
last_name: Smith

eve:
id: 4
first_name: Eve
last_name: Smith
4 changes: 4 additions & 0 deletions spec/lib/transmutation/serialization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ def initialize(id:)
Transmutation.max_depth = 2
end

after do
Transmutation.max_depth = 1
end

it "returns the maximum depth of the serializer" do
expect(Transmutation.max_depth).to eq(2)
end
Expand Down
Loading
Loading