From c8712447cbac828430bad0ed29cfe84b2c8cdea4 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Tue, 12 May 2015 16:19:57 -0700 Subject: [PATCH 1/4] Convert to ActiveAttr --- elasticsearch-persistence/README.md | 7 ++-- .../elasticsearch-persistence.gemspec | 2 +- .../examples/music/album.rb | 12 +++---- .../lib/elasticsearch/persistence/model.rb | 22 +++++------- .../elasticsearch/persistence/model/base.rb | 10 ++++-- .../model/time_with_zone_typecaster.rb | 36 +++++++++++++++++++ .../integration/model/model_basic_test.rb | 10 +++--- ...odel_test.rb => active_attr_model_test.rb} | 22 +++++++----- .../test/unit/model_base_test.rb | 2 +- .../test/unit/model_find_test.rb | 19 ++++------ .../test/unit/model_gateway_test.rb | 6 ++-- .../test/unit/model_rails_test.rb | 6 ++-- .../test/unit/model_store_test.rb | 19 ++++------ 13 files changed, 97 insertions(+), 76 deletions(-) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb rename elasticsearch-persistence/test/integration/repository/{virtus_model_test.rb => active_attr_model_test.rb} (82%) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 053366813..8aab8d2a0 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -462,11 +462,8 @@ require 'elasticsearch/persistence/model' #### Model Definition -The integration is implemented by including the module in a Ruby class. -The model attribute definition support is implemented with the -[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the -naming, validation, etc. features with the -[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem. +The integration is implemented by including the module in a Ruby class. The model attribute definition support is implemented with the [_ActiveAttr_](https://github.com/cgriego/active_attr) Rubygem, and the naming, +validation, etc. features with the [_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem. ```ruby class Article diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 2b7a7cbea..d7583668f 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_dependency "activesupport", '> 3' s.add_dependency "activemodel", '> 3' s.add_dependency "hashie" - s.add_dependency "virtus" + s.add_dependency "active_attr" s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake", "~> 11.1" diff --git a/elasticsearch-persistence/examples/music/album.rb b/elasticsearch-persistence/examples/music/album.rb index dc3dfdf07..d248342fc 100644 --- a/elasticsearch-persistence/examples/music/album.rb +++ b/elasticsearch-persistence/examples/music/album.rb @@ -1,5 +1,5 @@ class Meta - include Virtus.model + include ActiveAttr::Model attribute :rating attribute :have @@ -18,16 +18,16 @@ class Album end attribute :artist - attribute :artist_id, String, mapping: { index: 'not_analyzed' } - attribute :label, Hash, mapping: { type: 'object' } + attribute :artist_id, type: String, mapping: { index: 'not_analyzed' } + attribute :label, type: Hash, mapping: { type: 'object' } attribute :title - attribute :released, Date + attribute :released, type: Date attribute :notes attribute :uri - attribute :tracklist, Array, mapping: { type: 'object' } + attribute :tracklist, type: Array, mapping: { type: 'object' } attribute :styles - attribute :meta, Meta, mapping: { type: 'object' } + attribute :meta, type: Meta, mapping: { type: 'object' } end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 70b2ccb6b..5cce7635b 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -1,13 +1,13 @@ require 'active_support/core_ext/module/delegation' -require 'active_model' -require 'virtus' +require 'active_attr' require 'elasticsearch/persistence' require 'elasticsearch/persistence/model/base' require 'elasticsearch/persistence/model/errors' require 'elasticsearch/persistence/model/store' require 'elasticsearch/persistence/model/find' +require 'elasticsearch/persistence/model/time_with_zone_typecaster' module Elasticsearch module Persistence @@ -25,13 +25,7 @@ module Persistence module Model def self.included(base) base.class_eval do - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model + include ActiveAttr::Model extend ActiveModel::Callbacks define_model_callbacks :create, :save, :update, :destroy @@ -45,14 +39,14 @@ def self.included(base) extend Elasticsearch::Persistence::Model::Find::ClassMethods class << self - # Re-define the Virtus' `attribute` method, to configure Elasticsearch mapping as well + # Re-define the active_attr `attribute` method, to configure Elasticsearch mapping as well # - def attribute(name, type=nil, options={}, &block) + def attribute(name, options={}, &block) mapping = options.delete(:mapping) || {} super gateway.mapping do - indexes name, {type: Utils::lookup_type(type)}.merge(mapping) + indexes name, {type: Utils::lookup_type(options.fetch(:type, Object))}.merge(mapping) end gateway.mapping(&block) if block_given? @@ -122,8 +116,8 @@ def deserialize(document) # Set up common attributes # - attribute :created_at, Time, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, Time, default: lambda { |o,a| Time.now.utc } + attribute :created_at, type: ActiveSupport::TimeWithZone, default: lambda { Time.now.utc }, typecaster: TimeWithZoneTypecaster.new + attribute :updated_at, type: ActiveSupport::TimeWithZone, default: lambda { Time.now.utc }, typecaster: TimeWithZoneTypecaster.new attr_reader :hit end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb index bc55c583c..751522eb3 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb @@ -55,8 +55,12 @@ def _source @_source end + def to_h + attributes.symbolize_keys + end; alias :to_hash :to_h + def to_s - "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" + "#<#{self.class} #{to_h.inspect.gsub(/:(\w+)=>/, '\1: ')}>" end; alias :inspect :to_s end end @@ -75,9 +79,9 @@ def lookup_type(type) 'integer' when type == Float 'float' - when type == Date || type == Time || type == DateTime + when type == Date || type == Time || type == DateTime || type == ActiveSupport::TimeWithZone 'date' - when type == Virtus::Attribute::Boolean + when type == ActiveAttr::Typecasting::Boolean 'boolean' end end; module_function :lookup_type diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb new file mode 100644 index 000000000..586413a6b --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb @@ -0,0 +1,36 @@ +module Elasticsearch + module Persistence + module Model + class TimeWithZoneTypecaster + def initialize(precision = nil) + @precision_override = precision + end + + def call(value) + result = nil + if value.respond_to? :in_time_zone + result = value.in_time_zone + elsif value.respond_to? :to_s + result = Time.zone.parse(value) + end + + precision = nil + if !@precision_override.nil? + precision = @precision_override + elsif !ActiveSupport::JSON::Encoding.use_standard_json_time_format + precision = 0 + elsif Gem::Version.new(::Rails.version.to_s) < Gem::Version.new('4.0') + precision = 0 + elsif Gem::Version.new(::Rails.version.to_s) >= Gem::Version.new('4.0') && + Gem::Version.new(::Rails.version.to_s) < Gem::Version.new('4.1') + precision = 3 + else + precision = ActiveSupport::JSON::Encoding.time_precision + end + + result.change(usec: (result.usec / (10**(6-precision)))) + end + end + end + end +end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index b052114f8..28a964545 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -14,16 +14,16 @@ class ::Person settings index: { number_of_shards: 1 } document_type 'human_being' - attribute :name, String, + attribute :name, type: String, mapping: { fields: { name: { type: 'string', analyzer: 'snowball' }, raw: { type: 'string', analyzer: 'keyword' } } } - attribute :birthday, Date - attribute :department, String - attribute :salary, Integer - attribute :admin, Boolean, default: false + attribute :birthday, type: Date + attribute :department, type: String + attribute :salary, type: Integer + attribute :admin, type: Boolean, default: false validates :name, presence: true end diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/active_attr_model_test.rb similarity index 82% rename from elasticsearch-persistence/test/integration/repository/virtus_model_test.rb rename to elasticsearch-persistence/test/integration/repository/active_attr_model_test.rb index fbef15ba7..650cd19ad 100644 --- a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb +++ b/elasticsearch-persistence/test/integration/repository/active_attr_model_test.rb @@ -1,26 +1,30 @@ require 'test_helper' -require 'virtus' +require 'active_attr' module Elasticsearch module Persistence - class RepositoryWithVirtusIntegrationTest < Elasticsearch::Test::IntegrationTestCase + class RepositoryWithActiveAttrIntegrationTest < Elasticsearch::Test::IntegrationTestCase class ::Page - include Virtus.model + include ActiveAttr::Model - attribute :id, String, writer: :private - attribute :title, String - attribute :views, Integer, default: 0 - attribute :published, Boolean, default: false - attribute :slug, String, default: lambda { |page, attr| page.title.downcase.gsub(' ', '-') } + attribute :id, type: String, writer: :private + attribute :title, type: String + attribute :views, type: Integer, default: 0 + attribute :published, type: Boolean, default: false + attribute :slug, type: String, default: lambda { title.downcase.gsub(' ', '-') } def set_id(id) self.id = id end + + def to_hash + attributes.symbolize_keys + end end - context "The repository with a Virtus model" do + context "The repository with an active_attr model" do setup do @repository = Elasticsearch::Persistence::Repository.new do index :pages diff --git a/elasticsearch-persistence/test/unit/model_base_test.rb b/elasticsearch-persistence/test/unit/model_base_test.rb index 2d55e1620..40f06cbc1 100644 --- a/elasticsearch-persistence/test/unit/model_base_test.rb +++ b/elasticsearch-persistence/test/unit/model_base_test.rb @@ -9,7 +9,7 @@ class Elasticsearch::Persistence::ModelBaseTest < Test::Unit::TestCase class DummyBaseModel include Elasticsearch::Persistence::Model - attribute :name, String + attribute :name, type: String end end diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 4662cade8..39b0111a8 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -1,7 +1,6 @@ require 'test_helper' -require 'active_model' -require 'virtus' +require 'active_attr' require 'elasticsearch/persistence/model/errors' require 'elasticsearch/persistence/model/find' @@ -10,13 +9,7 @@ class Elasticsearch::Persistence::ModelFindTest < Test::Unit::TestCase context "The model find module," do class DummyFindModel - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model + include ActiveAttr::Model extend Elasticsearch::Persistence::Model::Find::ClassMethods @@ -24,10 +17,10 @@ class DummyFindModel define_model_callbacks :create, :save, :update, :destroy define_model_callbacks :find, :touch, only: :after - attribute :title, String - attribute :count, Integer, default: 0 - attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + attribute :title, type: String + attribute :count, type: Integer, default: 0 + attribute :created_at, type: DateTime, default: lambda { |o,a| Time.now.utc } + attribute :updated_at, type: DateTime, default: lambda { |o,a| Time.now.utc } end setup do diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index e3962ed40..b9f57ddfc 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -52,7 +52,7 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end end should "configure the mapping via attribute" do - DummyGatewayModel.attribute :name, String, mapping: { analyzer: 'snowball' } + DummyGatewayModel.attribute :name, type: String, mapping: { analyzer: 'snowball' } assert_respond_to DummyGatewayModel, :name assert_equal 'snowball', @@ -60,7 +60,7 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end end should "configure the mapping via an attribute block" do - DummyGatewayModel.attribute :name, String do + DummyGatewayModel.attribute :name, type: String do indexes :name, analyzer: 'custom' end @@ -74,7 +74,7 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end assert_equal 'integer', Elasticsearch::Persistence::Model::Utils::lookup_type(Integer) assert_equal 'float', Elasticsearch::Persistence::Model::Utils::lookup_type(Float) assert_equal 'date', Elasticsearch::Persistence::Model::Utils::lookup_type(Date) - assert_equal 'boolean', Elasticsearch::Persistence::Model::Utils::lookup_type(Virtus::Attribute::Boolean) + assert_equal 'boolean', Elasticsearch::Persistence::Model::Utils::lookup_type(ActiveAttr::Typecasting::Boolean) end should "remove IDs from hash when serializing" do diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb index 9ead42896..84611786a 100644 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -11,9 +11,9 @@ class ::MyRailsModel include Elasticsearch::Persistence::Model include Elasticsearch::Persistence::Model::Rails - attribute :name, String, mapping: { analyzer: 'string' } - attribute :published_at, DateTime - attribute :published_on, Date + attribute :name, type: String, mapping: { analyzer: 'string' } + attribute :published_at, type: DateTime + attribute :published_on, type: Date end class Application < Rails::Application diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb index 442ab56ee..b46717af8 100644 --- a/elasticsearch-persistence/test/unit/model_store_test.rb +++ b/elasticsearch-persistence/test/unit/model_store_test.rb @@ -1,7 +1,6 @@ require 'test_helper' -require 'active_model' -require 'virtus' +require 'active_attr' require 'elasticsearch/persistence/model/base' require 'elasticsearch/persistence/model/errors' @@ -11,13 +10,7 @@ class Elasticsearch::Persistence::ModelStoreTest < Test::Unit::TestCase context "The model store module," do class DummyStoreModel - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model + include ActiveAttr::Model include Elasticsearch::Persistence::Model::Base::InstanceMethods extend Elasticsearch::Persistence::Model::Store::ClassMethods @@ -27,10 +20,10 @@ class DummyStoreModel define_model_callbacks :create, :save, :update, :destroy define_model_callbacks :find, :touch, only: :after - attribute :title, String - attribute :count, Integer, default: 0 - attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + attribute :title, type: String + attribute :count, type: Integer, default: 0 + attribute :created_at, type: DateTime, default: lambda { Time.now.utc } + attribute :updated_at, type: DateTime, default: lambda { Time.now.utc } end setup do From 40319c1ac6c8dd1053cd21b688ce2ceefe8003e0 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Wed, 13 May 2015 10:21:54 -0700 Subject: [PATCH 2/4] Use ActiveSupport.version for version checks --- .../persistence/model/time_with_zone_typecaster.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb index 586413a6b..656782445 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb @@ -19,10 +19,10 @@ def call(value) precision = @precision_override elsif !ActiveSupport::JSON::Encoding.use_standard_json_time_format precision = 0 - elsif Gem::Version.new(::Rails.version.to_s) < Gem::Version.new('4.0') + elsif ActiveSupport.version < Gem::Version.new('4.0') precision = 0 - elsif Gem::Version.new(::Rails.version.to_s) >= Gem::Version.new('4.0') && - Gem::Version.new(::Rails.version.to_s) < Gem::Version.new('4.1') + elsif ActiveSupport.version >= Gem::Version.new('4.0') && + ActiveSupport.version < Gem::Version.new('4.1') precision = 3 else precision = ActiveSupport::JSON::Encoding.time_precision From 909cdd1eff2cb04ae9759508c17d6eb2afa4d6be Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Wed, 13 May 2015 11:31:13 -0700 Subject: [PATCH 3/4] Support version checking in rails 3 --- .../persistence/model/time_with_zone_typecaster.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb index 656782445..315403e70 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/time_with_zone_typecaster.rb @@ -19,10 +19,10 @@ def call(value) precision = @precision_override elsif !ActiveSupport::JSON::Encoding.use_standard_json_time_format precision = 0 - elsif ActiveSupport.version < Gem::Version.new('4.0') + elsif active_support_version < Gem::Version.new('4.0') precision = 0 - elsif ActiveSupport.version >= Gem::Version.new('4.0') && - ActiveSupport.version < Gem::Version.new('4.1') + elsif active_support_version >= Gem::Version.new('4.0') && + active_support_version < Gem::Version.new('4.1') precision = 3 else precision = ActiveSupport::JSON::Encoding.time_precision @@ -30,6 +30,14 @@ def call(value) result.change(usec: (result.usec / (10**(6-precision)))) end + + def active_support_version + if ActiveSupport.respond_to?(:version) + ActiveSupport.version + else + Gem::Version.new(ActiveSupport::VERSION::STRING) + end + end end end end From c05022be078b285925eae1126210f752d5a3d7f4 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Wed, 20 May 2015 09:57:47 -0700 Subject: [PATCH 4/4] Fix default lambda --- elasticsearch-persistence/test/unit/model_find_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 39b0111a8..da92cfa2b 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -19,8 +19,8 @@ class DummyFindModel attribute :title, type: String attribute :count, type: Integer, default: 0 - attribute :created_at, type: DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, type: DateTime, default: lambda { |o,a| Time.now.utc } + attribute :created_at, type: DateTime, default: lambda { Time.now.utc } + attribute :updated_at, type: DateTime, default: lambda { Time.now.utc } end setup do