diff --git a/lib/json_api_client/helpers/dynamic_attributes.rb b/lib/json_api_client/helpers/dynamic_attributes.rb index ba827c52..ac23ac3e 100644 --- a/lib/json_api_client/helpers/dynamic_attributes.rb +++ b/lib/json_api_client/helpers/dynamic_attributes.rb @@ -24,7 +24,7 @@ def []=(key, value) end def respond_to_missing?(method, include_private = false) - if (method.to_s =~ /^(.*)=$/) || has_attribute?(method) + if has_attribute?(method) || method.to_s.end_with?('=') true else super @@ -38,16 +38,19 @@ def has_attribute?(attr_name) protected def method_missing(method, *args, &block) - normalized_method = if key_formatter - key_formatter.unformat(method.to_s) - else - method.to_s - end - - if normalized_method =~ /^(.*)=$/ - set_attribute($1, args.first) - elsif has_attribute?(method) - attributes[method] + if has_attribute?(method) + self.class.class_eval do + define_method(method) do + attributes[method] + end + end + return send(method) + end + + normalized_method = safe_key_formatter.unformat(method.to_s) + + if normalized_method.end_with?('=') + set_attribute(normalized_method[0..-2], args.first) else super end @@ -61,10 +64,20 @@ def set_attribute(name, value) attributes[name] = value end + def safe_key_formatter + @safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new) + end + def key_formatter self.class.respond_to?(:key_formatter) && self.class.key_formatter end + class DefaultKeyFormatter + def unformat(method) + method.to_s + end + end + end end end diff --git a/test/unit/benchmark_dynamic_attributes_test.rb b/test/unit/benchmark_dynamic_attributes_test.rb new file mode 100644 index 00000000..54d31079 --- /dev/null +++ b/test/unit/benchmark_dynamic_attributes_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' +require 'benchmark' + +class BenchmarkDynamicAttributesTest < MiniTest::Test + def test_can_parse_global_meta_data + stub_request(:get, "http://example.com/articles/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Rails is Omakase" + } + }, + meta: { + copyright: "Copyright 2015 Example Corp.", + authors: [ + "Yehuda Katz", + "Steve Klabnik", + "Dan Gebhardt" + ] + }, + }.to_json) + + article = Article.find(1).first + + assert_equal "Rails is Omakase", article.title + assert_equal "1", article.id + + n = 10_000 + puts + Benchmark.bm do |x| + x.report('read: ') { n.times { article.title; article.id } } + x.report('write:') do + n.times do + article.title = 'New title' + article.better_title = 'Better title' + end + end + end + end +end diff --git a/test/unit/resource_test.rb b/test/unit/resource_test.rb index 7c003dcc..edb4e4ca 100644 --- a/test/unit/resource_test.rb +++ b/test/unit/resource_test.rb @@ -73,13 +73,13 @@ def test_formatted_key_accessors end with_altered_config(Article, :json_key_format => :underscored_key) do - article = Article.new("foo-bar" => "baz") + article = Article.new("foo-bam" => "baz") # Does not recognize dasherized attributes, fall back to hash syntax - refute article.respond_to? :foo_bar - assert_equal("baz", article.send("foo-bar")) - assert_equal("baz", article.send(:"foo-bar")) - assert_equal("baz", article["foo-bar"]) - assert_equal("baz", article[:"foo-bar"]) + refute article.respond_to? :foo_bam + assert_equal("baz", article.send("foo-bam")) + assert_equal("baz", article.send(:"foo-bam")) + assert_equal("baz", article["foo-bam"]) + assert_equal("baz", article[:"foo-bam"]) end end