Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specs & Integration for ActiveRecord and ROM #37

Merged
merged 34 commits into from
Nov 22, 2017
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
162bcbb
WIP
nesaulov Oct 18, 2017
9673e7d
[ActiveRecord] add tests for scopes
nesaulov Oct 26, 2017
4d56ce8
[ActiveRecord] Finish specs for AR
nesaulov Oct 27, 2017
3d53bc2
Clean up a bit
nesaulov Oct 27, 2017
328915e
Add gemfile for ruby 2.2
nesaulov Oct 28, 2017
b5a2c08
Add tweaks for AR 4.2 & ruby 2.2.0
nesaulov Oct 28, 2017
ad20095
Fix :policeman: configuration (maybe)
nesaulov Oct 28, 2017
6ace23a
Switch from rake rubocop task to hound ci
nesaulov Oct 29, 2017
d2f5ec7
[ActiveRecord] Add specs for scopes & associations for `Model.surreal…
nesaulov Oct 29, 2017
e9e5a31
[ActiveRecord] Fix NoMethodError message
nesaulov Oct 29, 2017
e41c380
[ActiveRecord] Fix NoMethodError | NameError situation
nesaulov Oct 29, 2017
52c8eb4
[ROM] Setup
nesaulov Oct 29, 2017
63961d1
Merge branch 'master' into orm_specs
nesaulov Oct 29, 2017
a463b68
[ROM] Implement & add a spec for instance serialization via ROM::Struct
nesaulov Oct 30, 2017
640bc40
[ROM] Fix ruby 2.2.0 issue
nesaulov Oct 30, 2017
1bbfa54
Merge branch 'master' into orm_specs
nesaulov Oct 31, 2017
b3b7a42
Introduce spec/support and shared contexts
nesaulov Oct 31, 2017
a792ec7
[ActiveRecord] improve specs a bit
nesaulov Oct 31, 2017
92bb6ff
Merge branch 'master' into orm_specs
nesaulov Oct 31, 2017
4558ac3
Merge branch 'master' into orm_specs
nesaulov Nov 1, 2017
f639b18
Merge branch 'master' into orm_specs
nesaulov Nov 3, 2017
fa52e9d
Patch ValueAssigner in a bit nasty way
nesaulov Nov 3, 2017
7437e81
Revert changes in ValueAssigner and mess up Builder again
nesaulov Nov 4, 2017
523d3ef
Merge branch 'master' into orm_specs
nesaulov Nov 4, 2017
bdde281
[ROM] Improve instances spec
nesaulov Nov 4, 2017
ef46072
Add workaround for Rubocop bug
nesaulov Nov 4, 2017
502ce4c
[ROM] Add collection specs
nesaulov Nov 6, 2017
5e15067
Refactor Builder
nesaulov Nov 6, 2017
04a12d7
Minor refactoring
nesaulov Nov 6, 2017
131b3c7
Merge branch 'master' into orm_specs
nesaulov Nov 16, 2017
9733bb6
Move `Schema` to `Builder`
nesaulov Nov 18, 2017
de2a0a7
Add specs for ROM class without schema
nesaulov Nov 18, 2017
9c3bc02
add spec to cover grandchild delegation subtleties
AlessandroMinali Nov 22, 2017
1e7a976
Merge pull request #49 from AlessandroMinali/orm_specs
nesaulov Nov 22, 2017
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ group :development, :test do
gem 'dry-types', '~> 0.12'
gem 'pry'
gem 'rom', '~> 3.0'
gem 'rom-repository'
gem 'rom-sql'
gem 'sequel'
gem 'sqlite3'
Expand Down
1 change: 1 addition & 0 deletions gemfiles/activerecord42.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ group :development, :test do
gem 'dry-struct', '~> 0.3'
gem 'dry-types', '~> 0.12'
gem 'rom', '~> 3.0'
gem 'rom-repository'
gem 'rom-sql'
gem 'sequel'
gem 'sqlite3'
Expand Down
26 changes: 23 additions & 3 deletions lib/surrealist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
module Surrealist
# Default namespaces nesting level
DEFAULT_NESTING_LEVEL = 666
# Instance variable name that is set by SchemaDefiner
INSTANCE_VARIABLE = '@__surrealist_schema'.freeze
# Instance's parent instance variable name that is set by SchemaDefiner
PARENT_VARIABLE = '@__surrealist_schema_parent'.freeze
# Class variable name that is set by SchemaDefiner
CLASS_VARIABLE = '@@__surrealist_schema'.freeze
# An instance of Carrier with default params
NULL_CARRIER = Carrier.new(false, false, false,
nil, DEFAULT_NESTING_LEVEL).freeze

class << self
# @param [Class] base class to include/extend +Surrealist+.
Expand All @@ -31,7 +40,7 @@ def included(base)
# Iterates over a collection of Surrealist Objects and
# maps surrealize to each record.
#
# @param [Object] Collection of instances of a class that has +Surrealist+ included.
# @param [Object] collection of instances of a class that has +Surrealist+ included.
# @param [Boolean] camelize optional argument for converting hash to camelBack.
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
# as instance's class name.
Expand Down Expand Up @@ -107,8 +116,7 @@ def surrealize_collection(collection, camelize: false, include_root: false, incl
# # => { name: 'Nikita', age: 23 }
# # For more examples see README
def build_schema(instance:, carrier:)
delegatee = instance.class.instance_variable_get('@__surrealist_schema_parent')
schema = (delegatee || instance.class).instance_variable_get('@__surrealist_schema')
schema = find_schema(instance)

Surrealist::ExceptionRaiser.raise_unknown_schema!(instance) if schema.nil?

Expand All @@ -121,5 +129,17 @@ def build_schema(instance:, carrier:)
hash = Builder.call(schema: normalized_schema, instance: instance)
carrier.camelize ? Surrealist::HashUtils.camelize_hash(hash) : hash
end

private

def find_schema(instance)
delegatee = instance.class.instance_variable_get(PARENT_VARIABLE)
maybe_schema = (delegatee || instance.class).instance_variable_get(INSTANCE_VARIABLE)
maybe_schema || (instance.class.class_variable_get(CLASS_VARIABLE) if klass_var_defined?(instance))
end

def klass_var_defined?(instance)
instance.class.class_variable_defined?(CLASS_VARIABLE)
end
end
end
33 changes: 29 additions & 4 deletions lib/surrealist/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Surrealist
# A class that builds a hash from the schema and type-checks the values.
class Builder
class << self
Schema = Struct.new(:key, :value).freeze
# A method that goes recursively through the schema hash, defines the values and type-checks them.
#
# @param [Hash] schema the schema defined in the object's class.
Expand All @@ -16,10 +17,9 @@ class << self
def call(schema:, instance:)
schema.each do |schema_key, schema_value|
if schema_value.is_a?(Hash)
Builder.call(schema: schema_value,
instance: instance.respond_to?(schema_key) ? instance.send(schema_key) : instance)
check_for_ar(schema, instance, schema_key, schema_value)
else
ValueAssigner.assign(schema: Schema.new(schema_key, schema_value),
ValueAssigner.assign(schema: Schema.new(schema_key, schema_value),
instance: instance) { |coerced_value| schema[schema_key] = coerced_value }
end
end
Expand All @@ -29,7 +29,32 @@ def call(schema:, instance:)
"in the schema that doesn't have a corresponding method."
end

Schema = Struct.new(:key, :value)
private

def check_for_ar(schema, instance, key, value)
if ar_collection?(instance, key)
construct_collection(schema, instance, key, value)
else
Builder.call(schema: value,
instance: instance.respond_to?(key) ? instance.send(key) : instance)
end
end

def ar_collection?(instance, schema_key)
defined?(ActiveRecord) &&
instance.respond_to?(schema_key) &&
instance.send(schema_key).is_a?(ActiveRecord::Relation)
end

def construct_collection(schema, instance, schema_key, schema_value)
schema[schema_key] = []
instance.send(schema_key).each do |i|
schema[schema_key] << call(
schema: Copier.deep_copy(hash: schema_value, carrier: Surrealist::NULL_CARRIER),
instance: i,
)
end
end
end
end
end
6 changes: 5 additions & 1 deletion lib/surrealist/schema_definer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ class SchemaDefiner
def self.call(klass, hash)
raise Surrealist::InvalidSchemaError, 'Schema should be defined as a hash' unless hash.is_a?(Hash)

klass.instance_variable_set('@__surrealist_schema', hash)
if klass.name =~ /ROM::Struct/
klass.class_variable_set('@@__surrealist_schema', hash)
Copy link
Collaborator

@AlessandroMinali AlessandroMinali Nov 17, 2017

Choose a reason for hiding this comment

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

This will probably cause undesired behavior with ROM(?)

Probably need similar spec for ROM similar to this one.

context 'inheritance of class that has delegated but we don\'t delegate' do
it 'raises RuntimeError' do
expect { FriendOfGuest.new.build_schema }
.to raise_error(Surrealist::UnknownSchemaError,
"Can't serialize FriendOfGuest - no schema was provided.")
end
end

I guess this brings up the problem of what is the minimum expected behaviour we want to test since we are not fully replicating all tests for each ORM.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I've added such spec. Talking about minimal behaviour to test, I think that ROM is pretty much covered right now, though it may seem that there are not so many examples there. AR tests are pretty massive because of scopes

else
klass.instance_variable_set('@__surrealist_schema', hash)
end
end
end
end
18 changes: 14 additions & 4 deletions lib/surrealist/value_assigner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class << self
# Assigns value returned from a method to a corresponding key in the schema hash.
#
# @param [Object] instance the instance of the object which methods from the schema are called on.
# @param [Struct] containg a single schema key and value
# @param [Struct] schema containing a single schema key and value
#
# @return [Hash] schema
def assign(instance:, schema:)
Expand All @@ -30,13 +30,23 @@ def assign(instance:, schema:)
# Generates first pass of serializing value, doing type check and coercion
#
# @param [Object] instance the instance of the object which methods from the schema are called on.
# @param [Struct] containing a single schema key and value
#
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
# @param [Struct] schema containing a single schema key and value
#
# @return [Object] value to be further processed
def raw_value(instance:, schema:)
value = instance.is_a?(Hash) ? instance[schema.key] : instance.send(schema.key)
coerce_value(value, schema: schema)
end

# Coerces value if type check is passed
#
# @param [Object] value the value to be checked and coerced
# @param [Struct] schema containing a single schema key and value
#
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
#
# @return [Object] value to be further processed
def coerce_value(value, schema:)
unless TypeHelper.valid_type?(value: value, type: schema.value)
raise Surrealist::InvalidTypeError,
"Wrong type for key `#{schema.key}`. Expected #{schema.value}, got #{value.class}."
Expand Down
2 changes: 0 additions & 2 deletions spec/carrier_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# frozen_string_literal: true

require_relative './carriers/params'

RSpec.describe Surrealist::Carrier do
describe '#call' do
context 'invalid arguments' do
Expand Down
Loading