gift_wrap 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 37ded86a1243c84591148c1c278126e00eca4c05
4
+ data.tar.gz: cc8f73fc149fdc246b96b43fa597d250562d429e
5
+ SHA512:
6
+ metadata.gz: a1b13e46b2b4c1eae2aa13e6b5d3c386ae4c2efd211ca6b015c3c7e86edebd9e96ab8ba069e0dd51c9edf594ae7c6a7b5b9547ef0b11662b439af1300e65df46
7
+ data.tar.gz: 782ab93cb6d43ffdd15faa770ee183a06f94d56a05a0442c2df21adfce77dca24256ef8d4fbe8152a197f138ca40d9c8e3b27719618b3958cbf244bc318b2469
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gift_wrap (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activemodel (4.2.6)
10
+ activesupport (= 4.2.6)
11
+ builder (~> 3.1)
12
+ activerecord (4.2.6)
13
+ activemodel (= 4.2.6)
14
+ activesupport (= 4.2.6)
15
+ arel (~> 6.0)
16
+ activesupport (4.2.6)
17
+ i18n (~> 0.7)
18
+ json (~> 1.7, >= 1.7.7)
19
+ minitest (~> 5.1)
20
+ thread_safe (~> 0.3, >= 0.3.4)
21
+ tzinfo (~> 1.1)
22
+ ansi (1.5.0)
23
+ arel (6.0.3)
24
+ builder (3.2.2)
25
+ i18n (0.7.0)
26
+ json (1.8.3)
27
+ minitest (5.9.0)
28
+ minitest-reporters (1.1.9)
29
+ ansi
30
+ builder
31
+ minitest (>= 5.0)
32
+ ruby-progressbar
33
+ rake (10.5.0)
34
+ ruby-progressbar (1.8.1)
35
+ sqlite3 (1.3.11)
36
+ thread_safe (0.3.5)
37
+ turn-again-reporter (1.1.0)
38
+ minitest-reporters (~> 1.0, >= 1.0.8)
39
+ tzinfo (1.2.2)
40
+ thread_safe (~> 0.1)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ activemodel (~> 4.0)
47
+ activerecord (~> 4.0, >= 4.0.0)
48
+ bundler (~> 1.7)
49
+ gift_wrap!
50
+ minitest-reporters (~> 1.1)
51
+ rake (~> 10.0)
52
+ sqlite3 (~> 1.3, >= 1.3.0)
53
+ turn-again-reporter (~> 1.1, >= 1.1.0)
54
+
55
+ BUNDLED WITH
56
+ 1.12.5
data/LICENSE.md ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2016, Paul Kwiatkowski
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of adalog nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # GiftWrap
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = Dir[File.join(File.dirname(__FILE__), 'test/**/*test.rb')]
7
+ end
data/lib/gift_wrap.rb ADDED
@@ -0,0 +1,16 @@
1
+ module GiftWrap
2
+
3
+ def self.config
4
+ @config ||= GiftWrap::Configuration.new
5
+ end
6
+
7
+
8
+ def self.configure
9
+ yield(config)
10
+ end
11
+
12
+ end
13
+
14
+ Dir[File.join(File.dirname(__FILE__), "gift_wrap", "*.rb")].each do |rb_file|
15
+ require rb_file
16
+ end
@@ -0,0 +1,54 @@
1
+ module GiftWrap
2
+ module ActiveRecordPresenter
3
+
4
+ def self.included(base)
5
+ base.send(:include, ::GiftWrap::Presenter)
6
+ base.extend(::GiftWrap::ActiveRecordPresenter::ClassMethods)
7
+ end
8
+
9
+
10
+ module ClassMethods
11
+
12
+ ##
13
+ # Sets delegate methods via ::unwrap_for en masse for all columns.
14
+ # The :attribute keyword argument can specify which columns are attributes
15
+ # in one of three ways from different values:
16
+ # - true: all columns will be considered attributes
17
+ # - false: no columns will be considered attributes
18
+ # - A hash with the key :only, whose value specifies which columns to consider as
19
+ # attributes, either singular or as an array of column names.
20
+ # - A hash with the key :except, whose value specifies which columns to consider as
21
+ # attributes, either singular or as an array of column names.
22
+ def unwrap_columns_for(active_record_model, attribute: true, **options)
23
+ columns = active_record_model.columns.map { |col| col.name.to_sym }
24
+ if true == attribute || false == attribute
25
+ unwrap_for(*columns, attribute: attribute, **options)
26
+ elsif Hash === attribute
27
+ as_attributes, not_attributes = partition_columns_for_attributes(columns, attribute)
28
+ unwrap_for(*as_attributes, attribute: true, **options)
29
+ unwrap_for(*not_attributes, attribute: false, **options)
30
+ else
31
+ unwrap_for(*columns, attribute: attribute, **options)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def partition_columns_for_attributes(columns, attribute_options)
38
+ partitioned =
39
+ if attribute_options.key?(:only)
40
+ accepted_columns = [attribute_options.fetch(:only)].flatten
41
+ columns.partition { |col| accepted_columns.include?(col) }
42
+ elsif attribute_options.key?(:except)
43
+ rejected_columns = [attribute_options.fetch(:except)].flatten
44
+ columns.partition { |col| !rejected_columns.include?(col) }
45
+ else
46
+ [columns, []]
47
+ end
48
+ return *partitioned
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ module GiftWrap
2
+ class Configuration
3
+
4
+ attr_accessor :use_serializers
5
+
6
+ def initialize
7
+ @use_serializers = !!defined?(ActiveModel)
8
+ end
9
+
10
+ def use_serializers?
11
+ @use_serializers
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,126 @@
1
+ module GiftWrap
2
+ module Presenter
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ if GiftWrap.config.use_serializers? && defined? ActiveModel::Serializers::JSON
7
+ base.send(:include, ActiveModel::Serializers::JSON)
8
+ end
9
+ end
10
+
11
+ ##
12
+ # Current options:
13
+ # - associations: a hash mapping association names to the presenter class
14
+ # to use when wrapping the association. Used to override the :with setting of
15
+ # a call to ::wrap_association at an instance level.
16
+ def initialize(wrapped_object, **options)
17
+ @wrapped_object = wrapped_object
18
+ @wrapped_association_presenters = options.fetch(:associations, {})
19
+ end
20
+
21
+ ##
22
+ # Used in methods defined by ::wrap_association to determine the presenter class that
23
+ # is used for a particular association name. First checks any instance-specific options
24
+ # for the association name, and falls back to those defined by the :with option passed
25
+ # to any ::wrap_association call for said association_name.
26
+ def wrapped_association_presenter(association_name)
27
+ if @wrapped_association_presenters.none?
28
+ self.class.wrapped_association_defaults.fetch(association_name) do |name|
29
+ raise NoMethodError.new("No association registered as '#{name}'.")
30
+ end
31
+ else
32
+ @wrapped_association_presenters.fetch(
33
+ association_name,
34
+ self.class.wrapped_association_defaults.fetch(association_name) do |name|
35
+ raise NoMethodError.new("No association registered as '#{name}'.")
36
+ end)
37
+ end
38
+ end
39
+
40
+ ##
41
+ # For use by ActiveModel::Serializers::JSON in building a default set of values
42
+ # when calling #as_json or #to_json
43
+ def attributes
44
+ self.class.attributes.each.with_object({}) do |msg, attr_hash|
45
+ attr_hash[msg.to_s] = self.send(msg)
46
+ end
47
+ end
48
+
49
+
50
+ module ClassMethods
51
+
52
+ ##
53
+ # Contains the of messages (which are used as hash keys) to send to self and collect
54
+ # when building an attributes hash.
55
+ def attributes
56
+ @attributes ||= Set.new
57
+ end
58
+
59
+ ##
60
+ # Contains the list of methods which will be delegated to the wrapped object rather than
61
+ # defined on presenter class itself.
62
+ def unwrapped_methods
63
+ @unwrapped_methods ||= Set.new
64
+ end
65
+
66
+ ##
67
+ # Contains the default settings for building any associations. These may be overridden
68
+ # on a per-intance basis.
69
+ def wrapped_association_defaults
70
+ @wrapped_association_defaults ||= {}
71
+ end
72
+
73
+ ##
74
+ # Defines a private method name by which the wrapped object may be referenced internally.
75
+ def wrapped_as(reference)
76
+ define_method(reference) do
77
+ @wrapped_object
78
+ end
79
+ send(:private, reference)
80
+ end
81
+
82
+ ##
83
+ # Declares one or more messages (method names) to be attributes.
84
+ def attribute(*names)
85
+ names.flatten.each do |name|
86
+ attributes << name
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Declares that one or more received messages (method calls) should be delegated directly
92
+ # to the wrapped object, and which may be optionally declared as attributes.
93
+ #
94
+ def unwrap_for(*names, attribute: false, **options)
95
+ names = names.flatten
96
+ names.each do |name|
97
+ unwrapped_methods << name
98
+ attributes << name if attribute
99
+ end
100
+ delegate(*names, to: :@wrapped_object)
101
+ end
102
+
103
+ ##
104
+ # Declares that the result of a delegated method call should be wrapped in another
105
+ # presenter, as defined by the :with keyword argument.
106
+ # This results in a method by the name of the first parameter by default, but may be
107
+ # customized with the :as keyword argument.
108
+ # Associations whose method produces an enumerable (ideally an Array) will have each
109
+ # item wrapped in the presenter and collected in an Array which is then returned.
110
+ def wrap_association(association, with: , as: association, **options)
111
+ wrapped_association_defaults[as] = with
112
+ define_method(as) do
113
+ presenter_class = wrapped_association_presenter(as)
114
+ associated = @wrapped_object.send(association)
115
+ if associated.respond_to?(:each)
116
+ associated.map { |assoc| presenter_class.new(assoc, **options) }
117
+ else
118
+ presenter_class.new(associated, **options)
119
+ end
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,3 @@
1
+ module GiftWrap
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,14 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :users, :force => true do |t|
5
+ t.string :email
6
+ t.string :first_name
7
+ t.string :last_name
8
+ t.string :encrypted_password
9
+ t.integer :sign_in_count
10
+
11
+ t.timestamps null: false
12
+ end
13
+
14
+ end
@@ -0,0 +1,18 @@
1
+ ##
2
+ # An object to associate with a map, for testing associations & wrapping thereof.
3
+ class Legend
4
+
5
+ def initialize(colored_regions, colored_lines)
6
+ @colored_regions = colored_regions
7
+ @colored_lines = colored_lines
8
+ end
9
+
10
+ def region_meaning(color)
11
+ @colored_regions[color]
12
+ end
13
+
14
+ def line_meaning(color)
15
+ @colored_lines[color]
16
+ end
17
+
18
+ end
@@ -0,0 +1,26 @@
1
+ ##
2
+ # Everyone loves maps.
3
+ class Map
4
+
5
+ attr_reader :type, :center, :units, :legend
6
+ attr_accessor :notes
7
+
8
+ def initialize(type, center, units, legend = :asshole_mapmaker_forgot_legend)
9
+ @type = type
10
+ @center = center
11
+ @units = units
12
+ @notes = ""
13
+ @legend = legend
14
+ end
15
+
16
+ def shows_roads?
17
+ maps_with_roads.include?(type)
18
+ end
19
+
20
+ private
21
+
22
+ def maps_with_roads
23
+ ['road', 'traffic', 'political']
24
+ end
25
+
26
+ end
@@ -0,0 +1,7 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ def initials
4
+ "#{first_name[0]}#{last_name[0]}"
5
+ end
6
+
7
+ end
@@ -0,0 +1,37 @@
1
+ module UserRecords
2
+
3
+ def self.create!
4
+ users_attributes.each do |attrs|
5
+ User.create(attrs)
6
+ end
7
+ end
8
+
9
+
10
+ def self.temporal_reference_point
11
+ @temporal_reference_point ||= Time.now
12
+ end
13
+
14
+
15
+ def self.users_attributes
16
+ [ { id: 1,
17
+ email: "paulwall@example.com",
18
+ first_name: "Paul",
19
+ last_name: "Wall",
20
+ encrypted_password: "$2a$10$DbRvyFxovWAly4ZCDtcJ6uVhbMGya2iGiLCURhSwM1ZGUyXpM5UiW",
21
+ sign_in_count: 2,
22
+ created_at: temporal_reference_point,
23
+ updated_at: temporal_reference_point,
24
+ },
25
+ { id: 2,
26
+ email: "gendoarrighetti@example.com",
27
+ first_name: "Gendo",
28
+ last_name: "Arrighetti",
29
+ encrypted_password: "$2a$10$DbRvyFxovWAly4ZCDtcJ6uVhbMGya2iGiLCURhSwM1ZGUyXpM5UiW",
30
+ sign_in_count: 0,
31
+ created_at: temporal_reference_point,
32
+ updated_at: temporal_reference_point,
33
+ },
34
+ ]
35
+ end
36
+
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'bundler/setup'
2
+ require 'minitest/autorun'
3
+ require 'minitest/reporters'
4
+ require 'minitest/reporters/turn_again_reporter'
5
+ Minitest::Reporters.use!(Minitest::Reporters::TurnAgainReporter.new)
6
+
7
+ ##
8
+ # The following active_support and active_model files are the specific minimum
9
+ # dependencies for using ActiveModel::Serializers::JSON
10
+ require 'active_support/json'
11
+ require 'active_support/concern'
12
+ require 'active_support/core_ext/class/attribute'
13
+ require 'active_model/naming'
14
+ require 'active_model/serialization'
15
+ require 'active_model/serializers/json'
16
+
17
+ ##
18
+ # And of course, require ourselves.
19
+ require 'gift_wrap'
20
+
21
+ ##
22
+ # Blatantly stolen from ActiveSupport::Testing::Declarative
23
+ # Allows for test files such as
24
+ # test "verify something" do
25
+ # ...
26
+ # end
27
+ # which become methods named test_verify_something, leaving a visual difference
28
+ # between tests themselves and any helper methods declared in the usual
29
+ # manner of `def some_helper_method`.
30
+ module DeclarativeTests
31
+ def test(name, &block)
32
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
33
+ defined = instance_method(test_name) rescue false
34
+ raise "#{test_name} is already defined in #{self}" if defined
35
+ if block_given?
36
+ define_method(test_name, &block)
37
+ else
38
+ define_method(test_name) do
39
+ flunk "No implementation provided for #{name}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class Minitest::Test
46
+ extend DeclarativeTests
47
+ end
48
+
49
+ ##
50
+ # ActiveRecord setup for testing use of GiftWrap::ActiveRecordPresenter
51
+ #
52
+ # TODO (possibly):
53
+ # Have some sort of method for switching on/off inclusion and execution
54
+ # of active_record in tests. The goal would be to ensure that no tests
55
+ # unrelated to ActiveRecord accidentally come to rely on its presence, or on
56
+ # the presence of the friends it pulls in (ActiveModel, ActiveSupport).
57
+ #
58
+ # Some ordering of some of these steps is important:
59
+ # 1. Set up in-memory sqlite3 connection.
60
+ # 2. Require the model class.
61
+ # 3. Load the database schema. Doing this before loading the model broke things?
62
+ # 4. Load a file that creates some user rows and run said creation method.
63
+ require 'active_record'
64
+ require 'sqlite3'
65
+
66
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
67
+ require File.join(File.dirname(__FILE__), "domain", "user.rb")
68
+ require File.join(File.dirname(__FILE__), "database", "schema.rb")
69
+ require File.join(File.dirname(__FILE__), "fixtures", "user_records.rb")
70
+ UserRecords.create!
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveRecordPresenterTest < Minitest::Test
4
+
5
+ ## Test Implementation Classes #################################################
6
+
7
+
8
+ class SimpleUserPresenter
9
+ include GiftWrap::ActiveRecordPresenter
10
+
11
+ unwrap_columns_for ::User
12
+
13
+ unwrap_for :initials
14
+
15
+ def email_with_display_name
16
+ "#{first_name} #{last_name} <#{email}>"
17
+ end
18
+
19
+ end
20
+
21
+
22
+ class NoAttributeUserPresenter
23
+ include GiftWrap::ActiveRecordPresenter
24
+ unwrap_columns_for ::User, attribute: false
25
+ end
26
+
27
+
28
+ class UserPresenterUsingOnly
29
+ include GiftWrap::ActiveRecordPresenter
30
+ unwrap_columns_for ::User, attribute: { only: [:first_name, :email, :last_name] }
31
+ end
32
+
33
+
34
+ class UserPresenterUsingExcept
35
+ include GiftWrap::ActiveRecordPresenter
36
+ unwrap_columns_for ::User, attribute: { except: [:first_name, :email, :last_name] }
37
+ end
38
+
39
+
40
+ ## Setup Helpers #########################################################
41
+
42
+
43
+ def sample_user
44
+ User.find(1)
45
+ end
46
+
47
+
48
+ ## Test Cases ##################################################################
49
+
50
+
51
+ test "can unwrap columns" do
52
+ paulwall = sample_user
53
+ presenter = SimpleUserPresenter.new(paulwall)
54
+ assert(presenter.respond_to?(:email))
55
+ assert(presenter.respond_to?(:first_name))
56
+ assert(presenter.respond_to?(:encrypted_password))
57
+ assert_equal(paulwall.email, presenter.email)
58
+ end
59
+
60
+
61
+ test "can respond to additional instance methods" do
62
+ paulwall = sample_user
63
+ presenter = SimpleUserPresenter.new(paulwall)
64
+ assert_equal("Paul Wall <paulwall@example.com>", presenter.email_with_display_name)
65
+ end
66
+
67
+
68
+ test "can wrap methods as expected of a presenter" do
69
+ paulwall = sample_user
70
+ presenter = SimpleUserPresenter.new(paulwall)
71
+ assert_equal("PW", paulwall.initials)
72
+ assert_equal("PW", presenter.initials)
73
+ end
74
+
75
+
76
+ test "columns are set as presenter attributes by default" do
77
+ paulwall = sample_user
78
+ presenter = SimpleUserPresenter.new(paulwall)
79
+ assert_includes(presenter.attributes.keys, 'email')
80
+ assert_includes(presenter.attributes.keys, 'created_at')
81
+ assert_includes(presenter.attributes.keys, 'updated_at')
82
+ assert_includes(presenter.attributes.keys, 'first_name')
83
+ assert_includes(presenter.attributes.keys, 'last_name')
84
+ assert_includes(presenter.attributes.keys, 'sign_in_count' )
85
+ end
86
+
87
+
88
+ test "unwrap_columns_for can be asked to set no columns as attributes" do
89
+ presenter = NoAttributeUserPresenter.new(sample_user)
90
+ assert_equal({}, presenter.attributes)
91
+ end
92
+
93
+
94
+ test "unwrap_columns_for can specify attributes with :only" do
95
+ presenter = UserPresenterUsingOnly.new(sample_user)
96
+ assert_includes(presenter.attributes.keys, 'email')
97
+ assert_includes(presenter.attributes.keys, 'first_name')
98
+ assert_includes(presenter.attributes.keys, 'last_name')
99
+ refute_includes(presenter.attributes.keys, 'created_at')
100
+ refute_includes(presenter.attributes.keys, 'updated_at')
101
+ refute_includes(presenter.attributes.keys, 'sign_in_count')
102
+ end
103
+
104
+
105
+ test "unwrap_columns_for can specify attributes with :except" do
106
+ presenter = UserPresenterUsingExcept.new(sample_user)
107
+ assert_includes(presenter.attributes.keys, 'created_at')
108
+ assert_includes(presenter.attributes.keys, 'updated_at')
109
+ assert_includes(presenter.attributes.keys, 'sign_in_count')
110
+ refute_includes(presenter.attributes.keys, 'email')
111
+ refute_includes(presenter.attributes.keys, 'first_name')
112
+ refute_includes(presenter.attributes.keys, 'last_name')
113
+ end
114
+
115
+ end
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+ require 'domain/map'
3
+ require 'domain/legend'
4
+
5
+ class ConfigurationTest < Minitest::Test
6
+
7
+ ## Setup Helpers #################################################
8
+
9
+ ##
10
+ # Since we're reconfiguring module-wide settings, we can't rely on a class defined
11
+ # at load time to pick up on changes to the configuration during successive test cases.
12
+ def build_presenter_class
13
+ Class.new do
14
+ include GiftWrap::Presenter
15
+ unwrap_for :type
16
+ unwrap_for :units, attribute: true
17
+ end
18
+ end
19
+
20
+
21
+ ## Test Cases ##################################################################
22
+
23
+
24
+ test "presenters include serializers when use_serializers is true" do
25
+ GiftWrap.configure do |config|
26
+ config.use_serializers = true
27
+ end
28
+ map = Map.new("physical", ["here", "there"], "mi")
29
+ presenter = build_presenter_class.new(map)
30
+ assert_kind_of(ActiveModel::Serializers::JSON, presenter)
31
+ end
32
+
33
+
34
+ test "presenters do not include serializers when use_serializers is false" do
35
+ GiftWrap.configure do |config|
36
+ config.use_serializers = false
37
+ end
38
+ map = Map.new("physical", ["here", "there"], "mi")
39
+ presenter = build_presenter_class.new(map)
40
+ refute_kind_of(ActiveModel::Serializers::JSON, presenter)
41
+ end
42
+
43
+ end
@@ -0,0 +1,259 @@
1
+ require 'test_helper'
2
+ require 'domain/map'
3
+ require 'domain/legend'
4
+
5
+ class PresenterTest < Minitest::Test
6
+
7
+ ## Test Implementation Classes #################################################
8
+
9
+
10
+ ##
11
+ # A presenter which adds a few new methods and attributes to a given Map.
12
+ class SimpleMapPresenter
13
+ include GiftWrap::Presenter
14
+
15
+ attribute :metric?
16
+
17
+ unwrap_for :type
18
+ unwrap_for :units, attribute: true
19
+
20
+ def metric?
21
+ metric_map_units.include?(units)
22
+ end
23
+
24
+ def contains_region?(region_name)
25
+ false # Implementation not important
26
+ end
27
+
28
+ private
29
+
30
+ def metric_map_units
31
+ ['m', 'km']
32
+ end
33
+
34
+ end
35
+
36
+ ##
37
+ # Gives an explicit name to the wrapped object for reference
38
+ # internally to the class, and has a method which uses that reference
39
+ # for use in proving that an internal reference is functioning.
40
+ class ExplicitReferencePresenter
41
+ include GiftWrap::Presenter
42
+
43
+ wrapped_as :explicit_reference
44
+
45
+ def uses_explicit_reference(msg_name)
46
+ explicit_reference.send(msg_name)
47
+ end
48
+
49
+ end
50
+
51
+ ##
52
+ # Presenter intended to work with a Legend.
53
+ class LegendPresenter
54
+ include GiftWrap::Presenter
55
+
56
+ unwrap_for :line_meaning
57
+
58
+ attribute :red_lines
59
+
60
+ def red_lines
61
+ line_meaning(:red)
62
+ end
63
+
64
+ def yellow_lines
65
+ line_meaning(:yellow)
66
+ end
67
+
68
+ def green_lines
69
+ line_meaning(:green)
70
+ end
71
+
72
+ end
73
+
74
+ ##
75
+ # Map Presenter which wraps its :legend association, for use in testing associations
76
+ # being wrapped in a presenter of their own.
77
+ class LegendaryMapPresenter
78
+ include GiftWrap::Presenter
79
+
80
+ unwrap_for :type, :units
81
+
82
+ wrap_association :legend, with: LegendPresenter
83
+
84
+ def metric?
85
+ metric_map_units.include?(units)
86
+ end
87
+
88
+ private
89
+
90
+ def metric_map_units
91
+ ['m', 'km']
92
+ end
93
+
94
+ end
95
+
96
+ ##
97
+ # Another presenter for legends as an alternative to test if overriding association
98
+ # presenters on a per-instance basis functions.
99
+ class MisleadingLegendPresenter
100
+ include GiftWrap::Presenter
101
+
102
+ unwrap_for :line_meaning
103
+
104
+ def red_lines
105
+ "no congestion"
106
+ end
107
+
108
+ def yellow_lines
109
+ "no congestion"
110
+ end
111
+
112
+ def green_lines
113
+ "no congestion"
114
+ end
115
+
116
+ end
117
+
118
+ ##
119
+ # Map Presenter which wraps its :legend association with the name :foobar
120
+ class FoobarLegendMapPresenter
121
+ include GiftWrap::Presenter
122
+
123
+ unwrap_for :type, :units
124
+
125
+ wrap_association :legend, with: LegendPresenter, as: :foobar
126
+
127
+ def metric?
128
+ metric_map_units.include?(units)
129
+ end
130
+
131
+ private
132
+
133
+ def metric_map_units
134
+ ['m', 'km']
135
+ end
136
+
137
+ end
138
+
139
+
140
+ ## Setup Helpers #########################################################
141
+
142
+
143
+ def physical_map
144
+ Map.new("physical", ["here", "there"], "mi")
145
+ end
146
+
147
+
148
+ def map_with_legend
149
+ Map.new("traffic", "downtown", "km", traffic_legend)
150
+ end
151
+
152
+
153
+ def traffic_legend
154
+ Legend.new(
155
+ { beige: "land",
156
+ blue: "water"
157
+ },
158
+ { green: "no congestion",
159
+ yellow: "light congestion",
160
+ red: "heavy congestion",
161
+ black: "impassable"
162
+ })
163
+ end
164
+
165
+
166
+ ## Test Cases ##################################################################
167
+
168
+
169
+ test "unwrapped methods are delegated properly" do
170
+ map = physical_map
171
+ presenter = SimpleMapPresenter.new(map)
172
+ assert_equal(map.type, presenter.type)
173
+ assert_equal(map.units, presenter.units)
174
+ end
175
+
176
+
177
+ test "methods not explicitly unwrapped are not accessible" do
178
+ map = physical_map
179
+ presenter = SimpleMapPresenter.new(map)
180
+ assert(map.respond_to?(:center))
181
+ refute(presenter.respond_to?(:center))
182
+ end
183
+
184
+
185
+ test "attributes can include unwrapped methods" do
186
+ attributes = SimpleMapPresenter.new(physical_map).attributes
187
+ assert_includes(attributes.keys, 'units')
188
+ end
189
+
190
+
191
+ test "attributes do not include unwrapped methods by default" do
192
+ attributes = SimpleMapPresenter.new(physical_map).attributes
193
+ refute_includes(attributes.keys, 'type')
194
+ end
195
+
196
+
197
+ test "attributes hash include explicitly declared attributes" do
198
+ attributes = SimpleMapPresenter.new(physical_map).attributes
199
+ assert_includes(attributes.keys, 'metric?')
200
+ end
201
+
202
+
203
+ test "can reference a wrapped object internally via wrapped_as name" do
204
+ map = physical_map
205
+ presenter = ExplicitReferencePresenter.new(map)
206
+ assert_equal(map.type, presenter.uses_explicit_reference(:type))
207
+ assert_equal(map.units, presenter.uses_explicit_reference(:units))
208
+ assert_raises(NoMethodError) do |variable|
209
+ presenter.explicit_reference
210
+ end
211
+ end
212
+
213
+
214
+ test "associations can be wrapped with their own presenter class" do
215
+ map = map_with_legend
216
+ presenter = LegendaryMapPresenter.new(map)
217
+ assert(map.respond_to?(:legend))
218
+ assert(presenter.respond_to?(:legend))
219
+ assert(LegendPresenter === presenter.legend)
220
+ assert(presenter.legend.respond_to?(:line_meaning))
221
+ assert(presenter.legend.respond_to?(:red_lines))
222
+ refute_includes(presenter.legend.attributes.keys, 'line_meaning')
223
+ assert_includes(presenter.legend.attributes.keys, 'red_lines')
224
+ assert_equal(presenter.legend.green_lines, 'no congestion')
225
+ assert_equal(presenter.legend.red_lines, 'heavy congestion')
226
+ end
227
+
228
+
229
+ test "associations' class can be specified on a per-instance basis" do
230
+ map = map_with_legend
231
+ presenter = LegendaryMapPresenter.new(map, associations: {
232
+ legend: MisleadingLegendPresenter
233
+ })
234
+ assert(map.respond_to?(:legend))
235
+ assert(presenter.respond_to?(:legend))
236
+ assert(MisleadingLegendPresenter === presenter.legend)
237
+ assert(presenter.legend.respond_to?(:line_meaning))
238
+ assert(presenter.legend.respond_to?(:red_lines))
239
+ assert_equal(presenter.legend.green_lines, 'no congestion')
240
+ assert_equal(presenter.legend.red_lines, 'no congestion')
241
+ end
242
+
243
+
244
+ test "associations' name can be something other than assoication name" do
245
+ map = map_with_legend
246
+ presenter = FoobarLegendMapPresenter.new(map)
247
+ assert(map.respond_to?(:legend))
248
+ assert(presenter.respond_to?(:foobar))
249
+ assert(LegendPresenter === presenter.foobar)
250
+ # Most of this mirrors the other association tests for good measure.
251
+ assert(presenter.foobar.respond_to?(:line_meaning))
252
+ assert(presenter.foobar.respond_to?(:red_lines))
253
+ refute_includes(presenter.foobar.attributes.keys, 'line_meaning')
254
+ assert_includes(presenter.foobar.attributes.keys, 'red_lines')
255
+ assert_equal(presenter.foobar.green_lines, 'no congestion')
256
+ assert_equal(presenter.foobar.red_lines, 'heavy congestion')
257
+ end
258
+
259
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gift_wrap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Kwiatkowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: turn-again-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.1'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 1.1.0
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '1.1'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.1.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: activerecord
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '4.0'
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 4.0.0
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '4.0'
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 4.0.0
109
+ - !ruby/object:Gem::Dependency
110
+ name: sqlite3
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.3'
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 1.3.0
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '1.3'
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: 1.3.0
129
+ description: A simple Ruby presenter library, for those who enjoy a strong separation
130
+ of concerns. You include a module, call some class-level macro-style methods, and
131
+ suddenly you're presenting for a wrapped object. No magic. If your knowledge of
132
+ pattern names comes from the Rails ecosystem, you might have used the popular Draper
133
+ 'decorator' library. Think of this like that one, except the term 'presenter' is
134
+ a better fit.
135
+ email:
136
+ - paul@groupraise.com
137
+ executables: []
138
+ extensions: []
139
+ extra_rdoc_files: []
140
+ files:
141
+ - Gemfile
142
+ - Gemfile.lock
143
+ - LICENSE.md
144
+ - README.md
145
+ - Rakefile
146
+ - lib/gift_wrap.rb
147
+ - lib/gift_wrap/active_record_presenter.rb
148
+ - lib/gift_wrap/configuration.rb
149
+ - lib/gift_wrap/presenter.rb
150
+ - lib/gift_wrap/version.rb
151
+ - test/database/schema.rb
152
+ - test/domain/legend.rb
153
+ - test/domain/map.rb
154
+ - test/domain/user.rb
155
+ - test/fixtures/user_records.rb
156
+ - test/test_helper.rb
157
+ - test/unit/active_record_presenter_test.rb
158
+ - test/unit/configuration_test.rb
159
+ - test/unit/presenter_test.rb
160
+ homepage: https://github.com/swifthand/adalog
161
+ licenses:
162
+ - Revised BSD, see LICENSE.md
163
+ metadata: {}
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ requirements: []
179
+ rubyforge_project:
180
+ rubygems_version: 2.4.8
181
+ signing_key:
182
+ specification_version: 4
183
+ summary: A simple Ruby presenter library, for those who enjoy a strong separation
184
+ of concerns.
185
+ test_files:
186
+ - test/database/schema.rb
187
+ - test/domain/legend.rb
188
+ - test/domain/map.rb
189
+ - test/domain/user.rb
190
+ - test/fixtures/user_records.rb
191
+ - test/test_helper.rb
192
+ - test/unit/active_record_presenter_test.rb
193
+ - test/unit/configuration_test.rb
194
+ - test/unit/presenter_test.rb