ruby 虚拟属性案例示例

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ruby 虚拟属性案例示例相关的知识,希望对你有一定的参考价值。

# frozen_string_literal: true

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem 'activerecord'
  gem 'sqlite3', '~> 1.3.6'
end

require 'active_record'
require 'minitest/autorun'
require 'logger'

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.string :author_first_name
    t.string :author_last_name
    t.text :metadata
  end
end

class Post < ActiveRecord::Base
  # Virtual attributes with Ruby methods
  attr_accessor :simple_virtual_attribute_without_type_cast

  attr_reader :simple_virtual_attribute_with_type_cast

  def simple_virtual_attribute_with_type_cast=(value)
    @simple_virtual_attribute_with_type_cast = value.to_i
  end

  # Extends with Rails API to have more advanced features like type casting and dirty handling
  attribute :virtual_attribute_with_type_cast, :boolean

  # Use case of virtual attr: to on/off hooks
  before_validation if: :virtual_attribute_with_type_cast do
    self.simple_virtual_attribute_without_type_cast = 'Virtual attr has been set to true'
  end


  # Use case of virtual attr: decorator to store composite values
  # Simple virtual attribute with pre-processing values and store in persistent attributes

  def author_full_name
    [author_first_name, author_last_name].join(' ')
  end

  def author_full_name=(name)
    split = name.split(' ', 2)
    self.author_first_name = split.first
    self.author_last_name = split.last
  end


  # Custom type casting without introducing & registering new type in Rails

  attribute :metadata_raw_json
  serialize :metadata, Hash

  validate :metadata_raw_json_valid_format, if: -> { @metadata_raw_json.present? }

  def metadata_raw_json
    @metadata_raw_json || metadata.to_json
  end

  def metadata_raw_json=(value)
    @metadata_raw_json = value

    self.metadata = build_metadata_from(@metadata_raw_json)
  end

  private

  def build_metadata_from(json)
    try_parse_metadata!(json)
  rescue JSON::ParserError
    nil
  end

  def metadata_raw_json_valid_format
    errors.add(:metadata_raw_json, 'invalid JSON format') unless json?(@metadata_raw_json)
  end

  def json?(raw_json)
    try_parse_metadata!(raw_json).present?
  rescue JSON::ParserError
    false
  end

  # @raise JSON::ParserError
  def try_parse_metadata!(value)
    ActiveSupport::JSON.decode(value)
  end
end

class VirtualAttributesUseCase < Minitest::Test
  def test_simple_virtual_attribute
    assert_equal(
      '1',
      Post.new(simple_virtual_attribute_without_type_cast: '1')
        .simple_virtual_attribute_without_type_cast
    )

    assert_equal(
      1,
      Post.new(simple_virtual_attribute_without_type_cast: 1)
        .simple_virtual_attribute_without_type_cast
    )

    # No dirty enabled
    refute Post.new.respond_to?(:simple_virtual_attribute_without_type_cast_changed?)


    assert_equal(
      1,
      Post.new(simple_virtual_attribute_with_type_cast: '1')
        .simple_virtual_attribute_with_type_cast
    )

    assert_equal(
      1,
      Post.new(simple_virtual_attribute_with_type_cast: 1)
        .simple_virtual_attribute_with_type_cast
    )

  end

  def test_simple_virtual_attribute_with_custom_typecast
    assert_equal(
      1,
      Post.new(simple_virtual_attribute_with_type_cast: '1')
        .simple_virtual_attribute_with_type_cast
    )

    assert_equal(
      1,
      Post.new(simple_virtual_attribute_with_type_cast: 1)
        .simple_virtual_attribute_with_type_cast
    )
  end

  def test_attribute_api
    post = Post.new virtual_attribute_with_type_cast: '1'
    assert_equal true, post.virtual_attribute_with_type_cast, 'type casting works'
    assert post.virtual_attribute_with_type_cast_changed?, 'dirty module has been enabled'
  end

  # Most common usage of virtual attributes to pre-process new values before save
  def test_virtual_attribute_with_custom_processing
    post = Post.create! author_full_name: 'Paul Keen'
    assert_equal 'Paul', post.author_first_name
    assert_equal 'Keen', post.author_last_name

    post = Post.create! author_first_name: 'Paul', author_last_name: 'Keen'
    assert_equal 'Paul Keen', post.author_full_name
  end

  # Common usage of virtual attributes to enable/disable hooks
  def test_virtual_attribute_in_hooks
    post = Post.new virtual_attribute_with_type_cast: '1'
    post.valid?
    assert_equal 'Virtual attr has been set to true', post.simple_virtual_attribute_without_type_cast
  end

  # Some type casting operations could throw runtime errors or require some schema.
  # For this we need more advance cases
  def test_virtual_attributes_with_validation_input_before_cast
    post = Post.new metadata_raw_json: 'invalid JSON document'
    refute post.valid?

    post = Post.create! metadata: { existed: 'json' }
    post.update metadata_raw_json: 'invalid JSON document'

    refute post.valid?
    assert_equal({}, post.metadata)
    assert post.errors[:metadata_raw_json].present?

    post = Post.create! metadata: { existed: 'json' }
    post.update metadata_raw_json: '{ "valid": "json" }'

    assert post.valid?
    assert 'json', post.metadata['valid']

    post = Post.new metadata_raw_json: '{ "valid": "json" }'
    assert 'json', post.metadata['valid']

    post = Post.new metadata_raw_json: '{ "valid": "json" }', metadata: { invalid: 'false' }
    assert 'json', post.metadata['valid']

    post = Post.new metadata: { invalid: 'false' }, metadata_raw_json: '{ "valid": "json" }'
    assert 'json', post.metadata['valid']

    post = Post.new metadata: { invalid: 'false' }
    assert_equal '{"invalid":"false"}', post.metadata_raw_json
  end
end

以上是关于ruby 虚拟属性案例示例的主要内容,如果未能解决你的问题,请参考以下文章

Ruby中的案例陈述[重复]

在 Ruby 中以编程方式访问属性/方法注释

这个语法在ruby中是啥意思[重复]

雷林鹏分享:Ruby 类案例

Ruby 中的工厂方法

20201103Tomcat虚拟主机配置的案例大全