Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Make array like classes configurable #6

Conversation

toddnestor
Copy link

At @RaiseMe we have a large Rails project that uses Mongoid with mongo instead of ActiveRecord with a relational database. As such our collections return instances of Mongoid::Criteria instead of ActiveRecord::Relation, for example:

image

As a result when we use Blueprinter we have to call .to_a on our collections, such as UserBlueprint.render(User.all.to_a) instead of just doing UserBlueprint.render(User.all). This is both inconvenient, as well as having a performance impact since we can't utilize cursors when iterating over a collection. Calling #to_a causes it to load all the matching documents into memory before iterating over them.

Originally I attempted to fix this by making it treat anything Enumerable as an array (#2), but this was too general and could cause potential issues. Next I was going to make it just treat Mongoid::Criteria as arrays (see diff here), but while I was putting in that PR I realized it would make more sense to just make this configurable. Making it configurable will have zero impact on users currently using the gem, allow users to customize it to their own needs, and doesn't require adding the Mongoid gem to the dev dependencies for the Blueprinter gem, as well as alleviating the testing overhead there (I ended up stubbing out Mongoid::Criteria on my attempt for that to avoid requiring having mongo running, but still wasn't a great approach).

This PR is a much more minimal approach than those other two and hopefully it will be acceptable.

Let me know if you have any questions!

spec/integrations/base_spec.rb Outdated Show resolved Hide resolved
lib/blueprinter/helpers/type_helpers.rb Outdated Show resolved Hide resolved
end

def array_like?(object)
object.is_a?(Array) || active_record_relation?(object)
array_like_classes.any? do |klass|
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got rid of the array_like_class? helper method since all this method did was call that method, but if you prefer it for readability I can move this logic back to that method and just call that method from here.

def array_like_classes
[
Array,
defined?(ActiveRecord::Relation) && ActiveRecord::Relation,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's not defined this becomes nil and the .compact at the end gets rid of it.

Copy link

@ritikesh ritikesh Jan 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is config and won't change at runtime, should we consider overriding the default getter or setter and inject this behaviour there? that way, it'll be memoized and we won't create/compute this array at runtime for each render call.
PS: sprinkling a memoization logic here might look ugly since this is a helper class.


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)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's not really anything else to assert here. We already have tests for that cover a hash being handled properly, and hashes are "array-like" in that they are Enumerable, so here I just mimicked what would happen if you pass it a collection for which you didn't pass the class of to array_like_classes.

@toddnestor
Copy link
Author

@ritikesh once this gets merged in can we get a new version of the gem pushed?

def array_like_classes
[
Array,
defined?(ActiveRecord::Relation) && ActiveRecord::Relation,
Copy link

@ritikesh ritikesh Jan 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is config and won't change at runtime, should we consider overriding the default getter or setter and inject this behaviour there? that way, it'll be memoized and we won't create/compute this array at runtime for each render call.
PS: sprinkling a memoization logic here might look ugly since this is a helper class.

README.md Outdated
Comment on lines 76 to 89
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.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.array_like_classes = [Set]
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you pls create a changelog entry and bump the patch version?

CHANGELOG.md Show resolved Hide resolved
CHANGELOG.md Show resolved Hide resolved
lib/blueprinter/version.rb Outdated Show resolved Hide resolved
lib/blueprinter/version.rb Outdated Show resolved Hide resolved
lib/blueprinter/configuration.rb Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
@ritikesh ritikesh merged commit beaa395 into blueprinter-ruby:master Feb 3, 2023
@ritikesh
Copy link

ritikesh commented Feb 3, 2023

released version 1.1.0

@toddnestor toddnestor deleted the make-array-like-classes-configurable branch February 4, 2023 00:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants