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
21 changes: 13 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
activemodel-entity (0.8.0)
activemodel-entity (0.13.0)
actionpack (>= 7)
activemodel (>= 7)
activesupport (>= 7)
Expand Down Expand Up @@ -69,19 +69,23 @@ GEM
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
minitest (5.24.1)
mini_portile2 (2.8.9)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
mutex_m (0.2.0)
nokogiri (1.16.6-aarch64-linux)
nokogiri (1.19.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.6-arm-linux)
nokogiri (1.19.4-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.16.6-arm64-darwin)
nokogiri (1.19.4-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.16.6-x86-linux)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-darwin)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
parallel (1.25.1)
parser (3.3.4.0)
Expand All @@ -90,6 +94,7 @@ GEM
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
prism (1.9.0)
psych (5.2.6)
date
stringio
Expand Down
32 changes: 18 additions & 14 deletions lib/active_model/entity/parsers/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,49 @@ def set_attribute_from_json(name, value)
@attributes[name] = @attributes[name].with_value_from_json(value)
end

def assign_attributes_from_json(json)
instance_exec(json, &self.class.json_attributes_assigner)
end

# Class-level methods.
module ClassMethods
def from_json(json)
new.tap do |instance|
instance.assign_attributes_from_json(json)
end
end
end

def method_missing(method_name, *, &)
if method_name == :assign_attributes_from_json
setters = attributes.keys.map do |name|
# Compiled once per exact class (class-level ivars are not
# inherited), so an assigner compiled for a superclass can never
# shadow attributes that exist only on a subclass.
def json_attributes_assigner
@json_attributes_assigner ||= compile_json_attributes_assigner
end

private

def compile_json_attributes_assigner
setters = attribute_types.keys.map do |name|
<<~RUBY
#{name}_value = json[#{name.camelize(:lower).inspect}]
#{name}_value = json[#{name.camelize(:lower).to_sym.inspect}] if #{name}_value.nil?

self.set_attribute_from_json(
set_attribute_from_json(
#{name.inspect},
#{name}_value
)
RUBY
end

code = <<~RUBY
def assign_attributes_from_json(json)
->(json) do
#{setters.join("\n")}
end
RUBY

self.class.class_eval(code)
send(method_name, *, &)
else
super
class_eval(code, __FILE__, __LINE__)
end
end

def respond_to_missing?(method_name, include_private)
method_name == :assign_attributes_from_json || super
end
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions spec/active_model/entity/parsers/json_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ class Role
attribute :field_positions, :array, of: "ParsersTest::Position"
end

class Employee
include ActiveModel::Entity

attribute :field_name, :string
end

class Manager < Employee
attribute :field_department, :string
end

class Person
include ActiveModel::Entity

Expand Down Expand Up @@ -121,6 +131,17 @@ class Person
end
end

context "parsing into a subclass after the superclass has compiled its assigner" do
it "keeps subclass-only attributes" do
employee = ParsersTest::Employee.from_json({ "fieldName" => "emp" })
manager = ParsersTest::Manager.from_json({ "fieldName" => "mgr", "fieldDepartment" => "sales" })

expect(employee.field_name).to eq("emp")
expect(manager.field_name).to eq("mgr")
expect(manager.field_department).to eq("sales")
end
end

context "parsing symbolized json" do
let(:source) do
{
Expand Down
Loading