diff --git a/CHANGELOG.md b/CHANGELOG.md index fd761ec2..81af084e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## 1.0.0 - 2023/05/01 +## 1.1.0 - 2023/02/02 +* 🚀 [FEATURE] Allow configuring custom array-like classes to be treated as collections when serializing. More details can be found [here](https://github.com/blueprinter-ruby/blueprinter/pull/6). Thanks to [@toddnestor](https://github.com/toddnestor). +* 💅 [ENHANCEMENT] Reduce object allocations in fields calculations to save some memory. More details can be found [here](https://github.com/blueprinter-ruby/blueprinter/pull/5). Thanks to [@nametoolong](https://github.com/nametoolong). + +## 1.0.0 - 2023/01/05 * 🚀 [Official Release] Released [`blueprinter-rb`](https://rubygems.org/gems/blueprinter-rb) under a new organisation. More details can be found [here](https://github.com/procore/blueprinter/issues/288). ## 0.25.3 - 2021/03/03 diff --git a/README.md b/README.md index 833731be..8f554273 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,24 @@ This will result in JSON that looks something like this: ] ``` + +You can also configure other classes to be treated like collections. For example, if you are using Mongoid, you can configure it to treat `Mongoid::Criteria` objects as collections: + +```ruby +Blueprinter.configure do |config| + config.custom_array_like_classes = [Mongoid::Criteria] +end +``` + +Or if you wanted it to treat the `Set` class as a collection: + +```ruby +Blueprinter.configure do |config| + config.custom_array_like_classes = [Set] +end +``` + +--- diff --git a/lib/blueprinter/configuration.rb b/lib/blueprinter/configuration.rb index 3ceef245..36d105e6 100644 --- a/lib/blueprinter/configuration.rb +++ b/lib/blueprinter/configuration.rb @@ -2,7 +2,7 @@ module Blueprinter class Configuration - attr_accessor :association_default, :datetime_format, :deprecations, :field_default, :generator, :if, :method, :sort_fields_by, :unless, :extractor_default, :default_transformers + attr_accessor :association_default, :datetime_format, :deprecations, :field_default, :generator, :if, :method, :sort_fields_by, :unless, :extractor_default, :default_transformers, :custom_array_like_classes VALID_CALLABLES = %i(if unless).freeze @@ -18,6 +18,15 @@ def initialize @unless = nil @extractor_default = AutoExtractor @default_transformers = [] + @custom_array_like_classes = [] + end + + def array_like_classes + @array_like_classes ||= [ + Array, + defined?(ActiveRecord::Relation) && ActiveRecord::Relation, + *custom_array_like_classes + ].compact end def jsonify(blob) diff --git a/lib/blueprinter/helpers/type_helpers.rb b/lib/blueprinter/helpers/type_helpers.rb index dde3d8d6..632463ac 100644 --- a/lib/blueprinter/helpers/type_helpers.rb +++ b/lib/blueprinter/helpers/type_helpers.rb @@ -3,13 +3,11 @@ module Blueprinter module TypeHelpers private - def active_record_relation?(object) - !!(defined?(ActiveRecord::Relation) && - object.is_a?(ActiveRecord::Relation)) - end def array_like?(object) - object.is_a?(Array) || active_record_relation?(object) + Blueprinter.configuration.array_like_classes.any? do |klass| + object.is_a?(klass) + end end end end diff --git a/lib/blueprinter/version.rb b/lib/blueprinter/version.rb index 1f607521..5a9fdafc 100644 --- a/lib/blueprinter/version.rb +++ b/lib/blueprinter/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Blueprinter - VERSION = '1.0.0' + VERSION = '1.1.0' end diff --git a/spec/integrations/base_spec.rb b/spec/integrations/base_spec.rb index 78cc9636..e83a7961 100644 --- a/spec/integrations/base_spec.rb +++ b/spec/integrations/base_spec.rb @@ -58,6 +58,30 @@ end end + context 'Given passed object is array-like' do + let(:blueprint) { blueprint_with_block } + let(:additional_object) { OpenStruct.new(obj_hash.merge(id: 2)) } + let(:obj) { Set.new([object_with_attributes, additional_object]) } + + context 'and is an instance of a configured array-like class' do + before do + reset_blueprinter_config! + Blueprinter.configure { |config| config.custom_array_like_classes = [Set] } + end + after { reset_blueprinter_config! } + + it 'should return the expected array of hashes' do + should eq('[{"id":1,"position_and_company":"Manager at Procore"},{"id":2,"position_and_company":"Manager at Procore"}]') + end + end + + context 'and is not an instance of a configured array-like class' do + it 'should raise an error' do + expect { blueprint.render(obj) }.to raise_error(NoMethodError) + end + end + end + context 'Inside Rails project' do include FactoryBot::Syntax::Methods let(:obj) { create(:user) } @@ -383,6 +407,36 @@ def extract(association_name, object, _local_options, _options={}) end end end + + context 'Given passed object is an instance of a configured array-like class' do + let(:blueprint) do + Class.new(Blueprinter::Base) do + identifier :id + fields :make + end + end + let(:vehicle1) { build(:vehicle, id: 1) } + let(:vehicle2) { build(:vehicle, id: 2, make: 'Mediocre Car') } + let(:vehicle3) { build(:vehicle, id: 3, make: 'Terrible Car') } + let(:vehicles) { [vehicle1, vehicle2, vehicle3] } + let(:obj) { Set.new(vehicles) } + let(:result) do + vehicles_json = vehicles.map do |vehicle| + "{\"id\":#{vehicle.id},\"make\":\"#{vehicle.make}\"}" + end.join(',') + "[#{vehicles_json}]" + end + + before do + reset_blueprinter_config! + Blueprinter.configure do |config| + config.custom_array_like_classes = [Set] + end + end + after { reset_blueprinter_config! } + + it('returns the expected result') { should eq(result) } + end end end describe '::render_as_hash' do