aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/attribute_decorators_test.rb
blob: bc3e9a8cf564a74e3a57fe28b3360e56dcef15e5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
require 'cases/helper'

module ActiveRecord
  class AttributeDecoratorsTest < ActiveRecord::TestCase
    class Model < ActiveRecord::Base
      self.table_name = 'attribute_decorators_model'
    end

    class StringDecorator < SimpleDelegator
      def initialize(delegate, decoration = "decorated!")
        @decoration = decoration
        super(delegate)
      end

      def type_cast_from_user(value)
        "#{super} #{@decoration}"
      end

      alias type_cast_from_database type_cast_from_user
    end

    setup do
      @connection = ActiveRecord::Base.connection
      @connection.create_table :attribute_decorators_model, force: true do |t|
        t.string :a_string
      end
    end

    teardown do
      return unless @connection
      @connection.execute 'DROP TABLE IF EXISTS attribute_decorators_model'
      Model.attribute_type_decorations.clear
      Model.reset_column_information
    end

    test "attributes can be decorated" do
      model = Model.new(a_string: 'Hello')
      assert_equal 'Hello', model.a_string

      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }

      model = Model.new(a_string: 'Hello')
      assert_equal 'Hello decorated!', model.a_string
    end

    test "decoration does not eagerly load existing columns" do
      assert_no_queries do
        Model.reset_column_information
        Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
      end
    end

    test "undecorated columns are not touched" do
      Model.attribute :another_string, Type::String.new, default: 'something or other'
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }

      assert_equal 'something or other', Model.new.another_string
    end

    test "decorators can be chained" do
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
      Model.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) }

      model = Model.new(a_string: 'Hello!')

      assert_equal 'Hello! decorated! decorated!', model.a_string
    end

    test "decoration of the same type multiple times is idempotent" do
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }

      model = Model.new(a_string: 'Hello')
      assert_equal 'Hello decorated!', model.a_string
    end

    test "decorations occur in order of declaration" do
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
      Model.decorate_attribute_type(:a_string, :other) do |type|
        StringDecorator.new(type, 'decorated again!')
      end

      model = Model.new(a_string: 'Hello!')

      assert_equal 'Hello! decorated! decorated again!', model.a_string
    end

    test "decorating attributes does not modify parent classes" do
      Model.attribute :another_string, Type::String.new, default: 'whatever'
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
      child_class = Class.new(Model)
      child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) }
      child_class.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) }

      model = Model.new(a_string: 'Hello!')
      child = child_class.new(a_string: 'Hello!')

      assert_equal 'Hello! decorated!', model.a_string
      assert_equal 'whatever', model.another_string
      assert_equal 'Hello! decorated! decorated!', child.a_string
      # We are round tripping the default, and we don't undo our decoration
      assert_equal 'whatever decorated! decorated!', child.another_string
    end

    test "defaults are decorated on the column" do
      Model.attribute :a_string, Type::String.new, default: 'whatever'
      Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }

      column = Model.columns_hash['a_string']

      assert_equal 'whatever decorated!', column.default
    end

    class Multiplier < SimpleDelegator
      def type_cast_from_user(value)
        return if value.nil?
        value * 2
      end
      alias type_cast_from_database type_cast_from_user
    end

    test "decorating with a proc" do
      Model.attribute :an_int, Type::Integer.new
      type_is_integer = proc { |_, type| type.type == :integer }
      Model.decorate_matching_attribute_types type_is_integer, :multiplier do |type|
        Multiplier.new(type)
      end

      model = Model.new(a_string: 'whatever', an_int: 1)

      assert_equal 'whatever', model.a_string
      assert_equal 2, model.an_int
    end
  end
end