Skip to content

Commit

Permalink
refactored property
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiobayona committed Feb 21, 2024
1 parent 405f41b commit 28e5081
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 98 deletions.
23 changes: 3 additions & 20 deletions lib/esquema/builder.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative "property"
require_relative "virtual_column"

module Esquema
# The Builder class is responsible for building a schema for an ActiveRecord model.
Expand Down Expand Up @@ -60,29 +61,11 @@ def add_virtual_properties
required_properties.concat(virtual_properties.keys)

virtual_properties.each do |property_name, options|
virtual_prop = create_virtual_prop(property_name, options)
@properties[property_name] = Property.new(virtual_prop, options)
virtual_col = VirtualColumn.new(property_name, options)
@properties[property_name] = Property.new(virtual_col, options)
end
end

# Creates a virtual property.
#
# @param property_name [Symbol] The name of the property.
# @param options [Hash] The options for the property.
# @return [OpenStruct] The created virtual property.
def create_virtual_prop(property_name, options)
OpenStruct.new(name: property_name.to_s,
class_name: property_name.to_s.classify,
type: options[:type],
item_type: options.dig(:items, :type),
default: options[:default],
title: options[:title],
description: options[:description],
enum: options[:enum],
columns: [],
collection?: options[:type] == :array)
end

# Adds properties from columns to the schema.
def add_properties_from_columns
columns.each do |property|
Expand Down
39 changes: 23 additions & 16 deletions lib/esquema/property.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,24 @@ class Property

# Accessors for the attributes.
attr_accessor(*ATTRS)
attr_reader :property, :options
attr_reader :object, :options

# Initializes a new Property instance.
#
# @param property [Object] The property object.
# @param object [Object] The object to build the property for.
# An object can be any of the following instance types:
# An ActiveRecord column. Example: ActiveRecord::ConnectionAdapters::SQLite3::Column
# An ActiveRecord association reflection. Example: ActiveRecord::Reflection::BelongsToReflection
# A virtual property.
# @param options [Hash] Additional options for the property.
# @raise [ArgumentError] If the property does not have a name.
def initialize(property, options = {})
raise ArgumentError, "property must have a name" unless property.respond_to?(:name)
def initialize(object, options = {})
puts "*********************************"
puts "property: #{object.inspect}"
puts "*********************************"
raise ArgumentError, "property must have a name" unless object.respond_to?(:name)

@property = property
@object = object
@options = options
end

Expand All @@ -55,29 +62,29 @@ def as_json
#
# @return [String] The title attribute.
def build_title
options[:title].presence || property.name.to_s.humanize
options[:title].presence || object.name.to_s.humanize
end

# Builds the default attribute for the Property.
#
# @return [Object, nil] The default attribute.
def build_default
return unless property.respond_to?(:default)
return unless object.respond_to?(:default)

default_value = property.default || options[:default].presence
default_value = object.default || options[:default].presence

@default = TypeCaster.cast(property.type, default_value) unless default_value.nil?
@default = TypeCaster.cast(object.type, default_value) unless default_value.nil?
end

# Builds the type attribute for the Property.
#
# @return [String, nil] The type attribute.
def build_type
return DB_TO_JSON_TYPE_MAPPINGS[:array] if property.try(:collection?)
return DB_TO_JSON_TYPE_MAPPINGS[:array] if object.try(:collection?)

return unless property.respond_to?(:type)
return unless object.respond_to?(:type)

@type = DB_TO_JSON_TYPE_MAPPINGS[property.type]
@type = DB_TO_JSON_TYPE_MAPPINGS[object.type]
end

# Builds the description attribute for the Property.
Expand All @@ -91,13 +98,13 @@ def build_description
#
# @return [Hash, nil] The items attribute.
def build_items
return unless property.try(:collection?)
return unless object.try(:collection?)

case property.type
case object.type
when :array
{ type: DB_TO_JSON_TYPE_MAPPINGS[property.item_type] }
{ type: DB_TO_JSON_TYPE_MAPPINGS[object.item_type] }
else
Builder.new(property.klass).build_schema
Builder.new(object.klass).build_schema
end
end

Expand Down
44 changes: 44 additions & 0 deletions lib/esquema/virtual_column.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Esquema
class VirtualColumn
def initialize(property_name, options = {})
@property_name = property_name
@options = options
end

def name
@property_name.to_s
end

def class_name
@property_name.to_s.classify
end

def type
@options[:type]
end

def item_type
@options.dig(:items, :type)
end

def default
@options[:default]
end

def title
@options[:title]
end

def description
@options[:description]
end

def columns
[]
end

def collection?
@options[:type] == :array
end
end
end
138 changes: 76 additions & 62 deletions spec/esquema/property_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,104 @@
require "spec_helper"

RSpec.describe Esquema::Property do
let(:user) { stub_const "User", Class.new(ActiveRecord::Base) }

context "string column" do
let(:string_column) { user.column_for_attribute("name") }
let(:object) { double("object") }
let(:options) { {} }
subject { described_class.new(object, options) }

describe "#initialize" do
context "when property has a name" do
before { allow(object).to receive(:respond_to?).with(:name).and_return(true) }

it "sets the property and options" do
expect(subject.object).to eq(object)
expect(subject.options).to eq(options)
end
end

let(:property) { Esquema::Property.new(string_column) }
context "when property does not have a name" do
before { allow(object).to receive(:respond_to?).with(:name).and_return(false) }

it "includes the required keywords and values" do
expect(property.as_json).to eq({ type: "string", title: "Name" })
it "raises an ArgumentError" do
expect { subject }.to raise_error(ArgumentError, "property must have a name")
end
end
end

context "integer column" do
let(:integer_column) { user.column_for_attribute("group") }
let(:property) { Esquema::Property.new(integer_column) }
context "when property is an ActiveRecord column" do
let(:user) { stub_const "User", Class.new(ActiveRecord::Base) }
let(:property) { Esquema::Property.new(column) }

it "includes the required keywords and values" do
expect(property.as_json).to eq({ type: "integer", title: "Group" })
context "string column" do
let(:column) { user.column_for_attribute("name") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({ type: "string", title: "Name" })
end
end
end

context "string column with default" do
let(:string_column) { user.column_for_attribute("country") }
let(:property) { Esquema::Property.new(string_column) }
context "integer column" do
let(:column) { user.column_for_attribute("group") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "string",
title: "Country",
default: "United States of America"
})
it "includes the required keywords and values" do
expect(property.as_json).to eq({ type: "integer", title: "Group" })
end
end
end

context "boolean column with default value" do
let(:boolean_column) { user.column_for_attribute("active") }

let(:property) { Esquema::Property.new(boolean_column) }
context "string column with default" do
let(:column) { user.column_for_attribute("country") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "boolean",
title: "Active",
default: false
})
it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "string",
title: "Country",
default: "United States of America"
})
end
end
end

context "text column" do
let(:text_column) { user.column_for_attribute("bio") }
context "boolean column with default value" do
let(:column) { user.column_for_attribute("active") }

let(:property) { Esquema::Property.new(text_column) }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "string",
title: "Bio"
})
it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "boolean",
title: "Active",
default: false
})
end
end
end

context "datetime column" do
let(:datetime_column) { user.column_for_attribute("created_at") }

let(:property) { Esquema::Property.new(datetime_column) }
context "text column" do
let(:column) { user.column_for_attribute("bio") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "date-time",
title: "Created at"
})
it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "string",
title: "Bio"
})
end
end
end

context "date column" do
let(:date_column) { user.column_for_attribute("dob") }
context "date column" do
let(:column) { user.column_for_attribute("dob") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "date",
title: "Dob"
})
end
end

let(:property) { Esquema::Property.new(date_column) }
context "datetime column" do
let(:column) { user.column_for_attribute("created_at") }

it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "date",
title: "Dob"
})
it "includes the required keywords and values" do
expect(property.as_json).to eq({
type: "date-time",
title: "Created at"
})
end
end
end
end

0 comments on commit 28e5081

Please sign in to comment.