Skip to content

Commit 26aebdb

Browse files
committed
Add tests for Issue JSONAPI-Resources#1467: ActiveModel resources support
Issue JSONAPI-Resources#1467 reported that ActiveModel-based models fail with 'undefined method where' errors in v0.10.7+. Root cause: - The default JSONAPI::Resource inherits from ActiveRelationResource - ActiveRelationResource expects ActiveRecord query methods (where, order, etc.) - ActiveModel::Model doesn't provide these methods Solution: - ActiveModel-based models should use JSONAPI::BasicResource - BasicResource doesn't assume ActiveRecord and works with any model Tests added: 1. active_model_resource_test.rb - Shows BasicResource works with ActiveModel 2. active_model_broken_test.rb - Demonstrates the problem with ActiveRelationResource The tests confirm that using BasicResource is the correct approach for non-ActiveRecord models. Related: https://github.com/cerebris/jsonapi-resources/issues/1467
1 parent e057af8 commit 26aebdb

3 files changed

Lines changed: 183 additions & 0 deletions

File tree

test/fixtures/active_record.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2691,6 +2691,89 @@ class RobotResource < ::JSONAPI::Resource
26912691
end
26922692
end
26932693

2694+
# Models and Resources for testing Issue #1467: ActiveModel-based resources
2695+
# ActiveModel class without ActiveRecord
2696+
class SimpleModel
2697+
include ActiveModel::Model
2698+
include ActiveModel::Attributes
2699+
2700+
attribute :id, :integer
2701+
attribute :name, :string
2702+
attribute :description, :string
2703+
2704+
# Simple in-memory store for testing
2705+
@@store = {}
2706+
2707+
def self.reset_store!
2708+
@@store = {}
2709+
end
2710+
2711+
def self.create(attributes)
2712+
model = new(attributes)
2713+
@@store[model.id] = model
2714+
model
2715+
end
2716+
2717+
def self.find(id)
2718+
@@store[id]
2719+
end
2720+
2721+
def self.all
2722+
@@store.values
2723+
end
2724+
2725+
def self.find_by(attributes)
2726+
all.find { |model| attributes.all? { |key, value| model.send(key) == value } }
2727+
end
2728+
end
2729+
2730+
class SimpleModelResource < JSONAPI::BasicResource
2731+
model_name 'SimpleModel'
2732+
model_hint model: SimpleModel, resource: :simple_model
2733+
2734+
attribute :name
2735+
attribute :description
2736+
2737+
# Override find_by_key for non-ActiveRecord models
2738+
def self.find_by_key(key, options = {})
2739+
model = _model_class.find(key.to_i)
2740+
return nil unless model
2741+
new(model, options[:context])
2742+
end
2743+
2744+
# Override find_fragments for non-ActiveRecord models
2745+
def self.find_fragments(filters, options = {})
2746+
models = if filters.empty?
2747+
_model_class.all
2748+
else
2749+
# Simple filtering implementation
2750+
_model_class.all.select do |model|
2751+
filters.all? { |key, value| model.send(key).to_s == value.to_s }
2752+
end
2753+
end
2754+
2755+
models.each_with_object({}) do |model, hash|
2756+
resource = new(model, options[:context])
2757+
hash[resource.identity] = {
2758+
identity: resource.identity,
2759+
cache: nil,
2760+
attributes: nil,
2761+
related: nil
2762+
}
2763+
end
2764+
end
2765+
end
2766+
2767+
# This resource uses the default JSONAPI::Resource (ActiveRelationResource)
2768+
# which will fail with ActiveModel models - reproduces Issue #1467
2769+
class BrokenActiveModelResource < JSONAPI::Resource
2770+
model_name 'SimpleModel'
2771+
model_hint model: SimpleModel, resource: :broken_active_model
2772+
2773+
attribute :name
2774+
attribute :description
2775+
end
2776+
26942777
### PORO Data - don't do this in a production app
26952778
$breed_data = BreedData.new
26962779
$breed_data.add(Breed.new(0, 'persian'))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require File.expand_path('../../../test_helper', __FILE__)
2+
3+
# Test for Issue #1467: Demonstrate the problem with ActiveRelationResource and ActiveModel
4+
# https://github.com/cerebris/jsonapi-resources/issues/1467
5+
#
6+
# This test shows that using JSONAPI::Resource (which inherits from ActiveRelationResource)
7+
# with an ActiveModel-based model will fail because ActiveRelationResource expects
8+
# ActiveRecord methods like 'where'
9+
10+
class ActiveModelBrokenTest < ActiveSupport::TestCase
11+
def setup
12+
# Seed some test data in the mock store
13+
SimpleModel.reset_store!
14+
@model1 = SimpleModel.create(id: 1, name: 'Test 1', description: 'First test')
15+
@model2 = SimpleModel.create(id: 2, name: 'Test 2', description: 'Second test')
16+
end
17+
18+
def teardown
19+
SimpleModel.reset_store!
20+
end
21+
22+
# This test demonstrates the problem: JSONAPI::Resource (ActiveRelationResource)
23+
# attempts to use ActiveRecord methods on ActiveModel models
24+
def test_activerelation_resource_fails_with_activemodel
25+
resource_klass = BrokenActiveModelResource
26+
context = {}
27+
28+
# This WILL raise NoMethodError because ActiveRelationResource expects ActiveRecord methods
29+
# The error could be 'order', 'where', or other ActiveRecord-specific methods
30+
error = assert_raises(NoMethodError) do
31+
fragments = resource_klass.find_fragments({}, { context: context })
32+
end
33+
34+
# Verify it's trying to use ActiveRecord-like query methods
35+
assert_match(/undefined method.*(order|where|limit|offset)/, error.message)
36+
end
37+
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require File.expand_path('../../../test_helper', __FILE__)
2+
3+
# Test for Issue #1467: ActiveModel-based resources fail with 'where' method error
4+
# https://github.com/cerebris/jsonapi-resources/issues/1467
5+
#
6+
# This test reproduces the scenario where:
7+
# - A model uses ActiveModel::Model instead of ActiveRecord::Base
8+
# - The resource should work with BasicResource or proper configuration
9+
# - In v0.10.7+, these resources fail with "undefined method 'where'"
10+
11+
class ActiveModelResourceTest < ActiveSupport::TestCase
12+
def setup
13+
# Seed some test data in the mock store
14+
SimpleModel.reset_store!
15+
@model1 = SimpleModel.create(id: 1, name: 'Test 1', description: 'First test')
16+
@model2 = SimpleModel.create(id: 2, name: 'Test 2', description: 'Second test')
17+
end
18+
19+
def teardown
20+
SimpleModel.reset_store!
21+
end
22+
23+
# Test that ActiveModel-based resources can be found without using ActiveRecord methods
24+
def test_find_activemodel_resources
25+
resource_klass = SimpleModelResource
26+
context = {}
27+
28+
# This should not raise "undefined method 'where'"
29+
assert_nothing_raised do
30+
fragments = resource_klass.find_fragments({}, { context: context })
31+
assert fragments.any?, "Should find SimpleModel fragments"
32+
assert_equal 2, fragments.size, "Should find 2 fragments"
33+
end
34+
end
35+
36+
# Test that ActiveModel-based resources can be filtered
37+
def test_filter_activemodel_resources
38+
resource_klass = SimpleModelResource
39+
context = {}
40+
41+
# Test with a filter
42+
filters = { name: 'Test 1' }
43+
44+
# This should not attempt to call _model_class.where(name: 'Test 1')
45+
assert_nothing_raised do
46+
fragments = resource_klass.find_fragments(filters, { context: context })
47+
assert_equal 1, fragments.size, "Should find 1 filtered fragment"
48+
end
49+
end
50+
51+
# Test that ActiveModel-based resources work with find_by_key
52+
def test_find_by_key_activemodel_resources
53+
resource_klass = SimpleModelResource
54+
context = {}
55+
56+
# find_by_key should work for ActiveModel resources
57+
assert_nothing_raised do
58+
resource = resource_klass.find_by_key(1, context: context)
59+
assert resource, "Should find resource by key"
60+
assert_equal 'Test 1', resource.name
61+
end
62+
end
63+
end

0 commit comments

Comments
 (0)