rails_view_adapters 0.2.1

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +181 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.md +27 -0
  8. data/README.md +88 -0
  9. data/Rakefile +6 -0
  10. data/lib/rails_view_adapters/adapter.rb +34 -0
  11. data/lib/rails_view_adapters/adapter_base.rb +101 -0
  12. data/lib/rails_view_adapters/definition_proxy.rb +155 -0
  13. data/lib/rails_view_adapters/map.rb +46 -0
  14. data/lib/rails_view_adapters/version.rb +4 -0
  15. data/lib/rails_view_adapters.rb +13 -0
  16. data/rails_view_adapters.gemspec +35 -0
  17. data/spec/active_record_helper.rb +16 -0
  18. data/spec/active_record_support/db/migrate/20161031222934_create_users.rb +14 -0
  19. data/spec/active_record_support/db/migrate/20161031223219_create_posts.rb +11 -0
  20. data/spec/active_record_support/db/migrate/20161031223253_create_teams.rb +10 -0
  21. data/spec/active_record_support/fabricators/post_fabricator.rb +7 -0
  22. data/spec/active_record_support/fabricators/team_fabricator.rb +6 -0
  23. data/spec/active_record_support/fabricators/user_fabricator.rb +10 -0
  24. data/spec/active_record_support/models/post.rb +4 -0
  25. data/spec/active_record_support/models/team.rb +4 -0
  26. data/spec/active_record_support/models/user.rb +5 -0
  27. data/spec/integration/an_adapter_spec.rb +58 -0
  28. data/spec/lib/adapter_base_spec.rb +133 -0
  29. data/spec/lib/adapter_spec.rb +45 -0
  30. data/spec/lib/definition_proxy_spec.rb +242 -0
  31. data/spec/lib/map_spec.rb +99 -0
  32. data/spec/spec_helper.rb +19 -0
  33. data/spec/support/an_adapter.rb +72 -0
  34. data/spec/support/fuzzy_nested_match.rb +96 -0
  35. metadata +206 -0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/core_ext/time"
3
+ Time.zone = "UTC"
4
+
5
+ require "rails_view_adapters/version"
6
+ require "rails_view_adapters/adapter_base"
7
+ require "rails_view_adapters/map"
8
+ require "rails_view_adapters/definition_proxy"
9
+ require "rails_view_adapters/adapter"
10
+
11
+ # Namespace for rails_view_adapters
12
+ module RailsViewAdapters
13
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_view_adapters/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rails_view_adapters"
8
+ spec.version = RailsViewAdapters::VERSION
9
+ spec.authors = ["Bryan Hockey"]
10
+ spec.email = ["bhock@umich.edu"]
11
+
12
+ spec.summary = "Bidirectional mappings between models and external views for Rails."
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/mlibrary/rails_view_adapters"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+ spec.test_files = Dir["spec/**/*"]
23
+
24
+ spec.required_ruby_version = ">= 2.2.2"
25
+
26
+ spec.add_dependency "activerecord", "~>4.2.7.1"
27
+ spec.add_dependency "activesupport", "~>4.2.7.1"
28
+
29
+ spec.add_development_dependency "sqlite3"
30
+ spec.add_development_dependency "rspec-rails"
31
+ spec.add_development_dependency "faker"
32
+ spec.add_development_dependency "fabrication"
33
+ spec.add_development_dependency "pry"
34
+ spec.add_development_dependency "rubocop"
35
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require "active_record"
3
+ require "fabrication"
4
+
5
+ require "active_record_support/models/user"
6
+ require "active_record_support/models/post"
7
+ require "active_record_support/models/team"
8
+
9
+ require "active_record_support/fabricators/user_fabricator"
10
+ require "active_record_support/fabricators/post_fabricator"
11
+ require "active_record_support/fabricators/team_fabricator"
12
+
13
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
14
+ ActiveSupport::Deprecation.silenced = true
15
+ ActiveRecord::Migration.verbose = false
16
+ ActiveRecord::Migrator.migrate File.join(File.dirname(__FILE__), "active_record_support/db/migrate")
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ class CreateUsers < ActiveRecord::Migration
3
+ def change
4
+ create_table :users do |t|
5
+ t.string :name
6
+ t.time :join_date
7
+ t.string :secret
8
+ t.boolean :admin
9
+ t.references :team, foreign_key: true
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ class CreatePosts < ActiveRecord::Migration
3
+ def change
4
+ create_table :posts do |t|
5
+ t.text :body
6
+ t.references :user, foreign_key: true
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ class CreateTeams < ActiveRecord::Migration
3
+ def change
4
+ create_table :teams do |t|
5
+ t.string :name
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require "faker"
3
+
4
+ Fabricator(:post) do
5
+ body { Faker::Lorem.paragraph }
6
+ user { Fabricate(:user) }
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ require "faker"
3
+
4
+ Fabricator(:team) do
5
+ name { Faker::Company.name }
6
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require "faker"
3
+
4
+ Fabricator(:user) do
5
+ name { Faker::Name.name }
6
+ join_date { Faker::Time.between(2.years.ago, Time.now) }
7
+ secret { Faker::Internet.password }
8
+ admin false
9
+ team { Fabricate(:team) }
10
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class Post < ActiveRecord::Base
3
+ belongs_to :user
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class Team < ActiveRecord::Base
3
+ has_many :users
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ class User < ActiveRecord::Base
3
+ belongs_to :team
4
+ has_many :posts
5
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require "active_record_helper"
3
+ require "spec_helper"
4
+
5
+ module RailsViewAdapters
6
+
7
+ describe "an adapter", integration: true do
8
+ date_format = "%Y-%m-%dT%H:%M:%S%z"
9
+ secret_token = Faker::Internet.password
10
+ Adapter.define(:user_integration_test_adapter) do
11
+ map_simple :name, :author
12
+ map_date :join_date, :member_since, date_format
13
+ map_date :created_at, :created_at, date_format
14
+ map_date :updated_at, :updated_at, date_format
15
+ map_bool :admin, :super_user
16
+ hidden_field :secret
17
+ map_from_public :secret do |token|
18
+ { secret: token }
19
+ end
20
+ map_belongs_to :team, :favorite_team, model_class: Team
21
+ map_has_many :posts, :all_posts, sub_method: :body
22
+ end
23
+
24
+ before(:each) do
25
+ @model = Fabricate(:user, secret: secret_token)
26
+ Fabricate.times(3, :post, user: @model)
27
+
28
+ @public_hash = {
29
+ author: @model.name,
30
+ member_since: @model.join_date.in_time_zone.strftime(date_format),
31
+ super_user: @model.admin,
32
+ favorite_team: @model.team.id,
33
+ all_posts: @model.posts.pluck(:body),
34
+ created_at: @model.created_at.in_time_zone.strftime(date_format),
35
+ updated_at: @model.updated_at.in_time_zone.strftime(date_format)
36
+ }
37
+
38
+ @model_hash = {
39
+ name: @model.name,
40
+ join_date: @model.join_date,
41
+ admin: @model.admin,
42
+ secret: @model.secret,
43
+ team_id: @model.team_id,
44
+ posts: @model.posts,
45
+ created_at: @model.created_at,
46
+ updated_at: @model.updated_at
47
+ }
48
+ end
49
+
50
+ it_behaves_like "an adapter", secret: secret_token do
51
+ let(:adapter_class) { UserIntegrationTestAdapter }
52
+ let(:model) { @model }
53
+ let(:public_hash) { @public_hash }
54
+ let(:model_hash) { @model_hash }
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "active_record_helper"
4
+
5
+ module RailsViewAdapters
6
+ describe AdapterBase do
7
+ class TestAdapter < AdapterBase
8
+ def self.simple_maps
9
+ [[:body, :post_text]]
10
+ end
11
+
12
+ def self.to_maps
13
+ [[:user_id, proc {|v| { user_name: v + 10 } }]]
14
+ end
15
+
16
+ def self.from_maps
17
+ [[:user_name, proc {|v| { user_id: v + 100 } }]]
18
+ end
19
+
20
+ def self.model_fields
21
+ [:body, :user_id]
22
+ end
23
+
24
+ def self.public_fields
25
+ [:post_text, :user_name]
26
+ end
27
+ end
28
+
29
+ class ARTestAdapter < TestAdapter
30
+ def self.to_maps
31
+ [[:user_id, proc {|v| { user_name: User.find(v).name } }]]
32
+ end
33
+
34
+ def self.from_maps
35
+ [[:user_name, proc {|v| { user_id: User.find_by_name(v).id } }]]
36
+ end
37
+ end
38
+
39
+ describe "#to_model_hash" do
40
+ let(:internals) { { a: 1, b: 2 } }
41
+ it "returns the internals" do
42
+ expect(described_class.new(internals, {}).to_model_hash).to eql(internals)
43
+ end
44
+ end
45
+
46
+ describe "#to_params_hash" do
47
+ let(:internals) { { a: 1, b: 2 } }
48
+ let(:disjoint_extras) { { c: 3 } }
49
+ let(:overlapping_extras) { { b: 27, c: 3 } }
50
+ it "merges internals and extras" do
51
+ expect(described_class.new(internals, disjoint_extras).to_params_hash)
52
+ .to eql(a: 1, b: 2, c: 3)
53
+ end
54
+ it "prefers internals over extras" do
55
+ expect(described_class.new(internals, overlapping_extras).to_params_hash)
56
+ .to eql(a: 1, b: 2, c: 3)
57
+ end
58
+ it "symbolizes keys" do
59
+ expect(described_class.new(internals, "c" => 3).to_params_hash)
60
+ .to eql(a: 1, b: 2, c: 3)
61
+ end
62
+ end
63
+
64
+ describe "#to_public_hash" do
65
+ let(:internals) { { body: 2, user_id: 4 } }
66
+ let(:extras) { { extra_field: "asdf" } }
67
+ let(:adapter) { TestAdapter.new(internals, extras) }
68
+ it "returns the public hash w/o extras" do
69
+ expect(adapter.to_public_hash).to eql(
70
+ post_text: 2,
71
+ user_name: 14
72
+ )
73
+ end
74
+ end
75
+
76
+ describe "#to_json" do
77
+ let(:adapter) { described_class.new({}, {}) }
78
+ let(:public_hash) { double(:public_hash) }
79
+ let(:options) { { a: 11_313, b: 1231 } }
80
+ it "delegates to #to_json on the public hash" do
81
+ allow(adapter).to receive(:to_public_hash).and_return(public_hash)
82
+ expect(public_hash).to receive(:to_json).with(options)
83
+ adapter.to_json(options)
84
+ end
85
+ end
86
+
87
+ describe "::from_model" do
88
+ let(:model) { Fabricate(:post) }
89
+ it "instantiates the adapter" do
90
+ expect(TestAdapter.from_model(model)).to be_an_instance_of(TestAdapter)
91
+ end
92
+ it "loads the model as its internals" do
93
+ expect(TestAdapter.from_model(model).to_model_hash).to eql(
94
+ body: model.body,
95
+ user_id: model.user_id
96
+ )
97
+ end
98
+ end
99
+
100
+ describe "::from_public" do
101
+ let(:user) { Fabricate(:user) }
102
+ let(:input_hash) do
103
+ {
104
+ post_text: Faker::Lorem.paragraph,
105
+ user_name: user.name,
106
+ extra_field: 12_345
107
+ }
108
+ end
109
+ it "instantiates the adapter" do
110
+ expect(ARTestAdapter.from_public(input_hash)).to be_an_instance_of(ARTestAdapter)
111
+ end
112
+ it "loads the internals" do
113
+ expect(ARTestAdapter.from_public(input_hash).to_model_hash).to eql(
114
+ body: input_hash[:post_text],
115
+ user_id: user.id
116
+ )
117
+ end
118
+ it "loads the extras" do
119
+ expect(ARTestAdapter.from_public(input_hash).to_params_hash).to eql(
120
+ body: input_hash[:post_text],
121
+ user_id: user.id,
122
+ extra_field: 12_345
123
+ )
124
+ end
125
+ it "doesn't mutate the public representation" do
126
+ expect(ARTestAdapter.from_public(input_hash).to_public_hash).to eql(
127
+ post_text: input_hash[:post_text],
128
+ user_name: input_hash[:user_name]
129
+ )
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module RailsViewAdapters
5
+ describe Adapter do
6
+ describe "::define" do
7
+ it "defines a new class" do
8
+ Adapter.define(:new_test_adapter) {}
9
+ expect do
10
+ Object.const_get("NewTestAdapter")
11
+ end.to_not raise_error
12
+ end
13
+
14
+ it "sets the constant equal to the created adapter" do
15
+ spy = double(:spy)
16
+ allow(Adapter).to receive(:adapter_from_map).and_return(spy)
17
+ Adapter.define(:even_newer) {}
18
+ expect(EvenNewer).to eql(spy)
19
+ end
20
+ end
21
+
22
+ describe "::adapter_from_map" do
23
+ let(:map) do
24
+ double(:map,
25
+ model_fields: [:m1, :m2],
26
+ public_fields: [:p1, :p2],
27
+ simple_maps: [[:m1, :p1]],
28
+ to_maps: [[:m2, double(:proc1)]],
29
+ from_maps: [[:p2, double(:proc2)]])
30
+ end
31
+ let(:adapter) { Adapter.adapter_from_map(map) }
32
+ it "returns a new class" do
33
+ expect(adapter).to be_a(Class)
34
+ end
35
+ it "inherits from AdapterBase" do
36
+ expect(adapter).to be <= AdapterBase
37
+ end
38
+ [:model_fields, :public_fields, :simple_maps, :to_maps, :from_maps].each do |field|
39
+ it "adds the ::#{field} method" do
40
+ expect(adapter.public_send(field)).to eql(map.public_send(field))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "active_record_helper"
4
+
5
+ module RailsViewAdapters
6
+
7
+ class TestMap < Map
8
+ def to_hash
9
+ {
10
+ model_fields: model_fields,
11
+ public_fields: public_fields,
12
+ simple_maps: simple_maps,
13
+ to_maps: to_maps,
14
+ from_maps: from_maps
15
+ }
16
+ end
17
+ end
18
+
19
+ describe DefinitionProxy do
20
+ let(:proxy) { described_class.new(TestMap.new) }
21
+ let(:bare_hash) do
22
+ {
23
+ model_fields: [],
24
+ public_fields: [],
25
+ simple_maps: [],
26
+ to_maps: [],
27
+ from_maps: []
28
+ }
29
+ end
30
+
31
+ describe "#map_simple" do
32
+ let(:model_field) { :mod }
33
+ let(:public_field) { :pub }
34
+ it "creates the correct mapping" do
35
+ proxy.map_simple(model_field, public_field)
36
+ expect(proxy.map.to_hash).to eql(bare_hash.merge(
37
+ public_fields: [public_field],
38
+ model_fields: [model_field],
39
+ simple_maps: [[model_field, public_field]]
40
+ ))
41
+ end
42
+ end
43
+
44
+ describe "#map_to_public" do
45
+ let(:model_field) { :mod }
46
+ let(:extra_fields) { [:one, :two] }
47
+ let(:process) { proc {|v| { one: v + 1, two: v + 2 } } }
48
+ it "creates the correct mapping" do
49
+ proxy.map_to_public(model_field, extra_fields, &process)
50
+ expect(proxy.map.to_hash).to eql(bare_hash.merge(
51
+ to_maps: [[model_field, process]],
52
+ public_fields: extra_fields,
53
+ model_fields: [model_field]
54
+ ))
55
+ end
56
+ end
57
+
58
+ describe "#map_from_public" do
59
+ let(:public_field) { :pub }
60
+ let(:process) { proc {|v| { one: v + 1, two: v + 2 } } }
61
+ it "creates the correct mapping" do
62
+ proxy.map_from_public(public_field, &process)
63
+ expect(proxy.map.to_hash).to eql(bare_hash.merge(
64
+ from_maps: [[public_field, process]],
65
+ public_fields: [public_field]
66
+ ))
67
+ end
68
+ end
69
+
70
+ describe "#hidden_field" do
71
+ let(:hidden_field) { :hidden }
72
+ it "adds a model_field" do
73
+ proxy.hidden_field(hidden_field)
74
+ expect(proxy.map.to_hash).to eql(bare_hash.merge(
75
+ model_fields: [hidden_field]
76
+ ))
77
+ end
78
+ end
79
+
80
+ describe "#map_date" do
81
+ let(:model_field) { :mod }
82
+ let(:public_field) { :pub }
83
+ let(:date_format) { "%Y-%m-%dT%H:%M:%SZ" }
84
+ let(:time) { Time.new(2016, 10, 31, 12, 25, 33).utc }
85
+ let(:time_string) { time.strftime(date_format) }
86
+ it "creates the correct model_fields" do
87
+ proxy.map_date(model_field, public_field, date_format)
88
+ expect(proxy.map.model_fields).to eql([model_field])
89
+ end
90
+ it "creates the correct public_fields" do
91
+ proxy.map_date(model_field, public_field, date_format)
92
+ expect(proxy.map.public_fields).to eql([public_field])
93
+ end
94
+ it "creates no simple_maps" do
95
+ proxy.map_date(model_field, public_field, date_format)
96
+ expect(proxy.map.simple_maps).to eql([])
97
+ end
98
+ it "creates the correct to_maps" do
99
+ proxy.map_date(model_field, public_field, date_format)
100
+ expect(proxy.map.to_maps[0]).to contain_exactly(model_field, an_instance_of(Proc))
101
+ end
102
+ it "creates the correct from_maps" do
103
+ proxy.map_date(model_field, public_field, date_format)
104
+ expect(proxy.map.from_maps[0]).to contain_exactly(public_field, an_instance_of(Proc))
105
+ end
106
+ it "defines a to_map that converts the model's date to the public's string" do
107
+ proxy.map_date(model_field, public_field, date_format)
108
+ expect(proxy.map.to_maps[0][1].call(time)).to eql(public_field => time_string)
109
+ end
110
+ it "defines a from_map that converts the public's string to the model's Time" do
111
+ proxy.map_date(model_field, public_field, date_format)
112
+ expect(proxy.map.from_maps[0][1].call(time_string)).to eql(model_field => time)
113
+ end
114
+ it "raises ArgumentError if date_format is nil" do
115
+ expect do
116
+ proxy.map_date(model_field, public_field, nil)
117
+ end.to raise_error ArgumentError
118
+ end
119
+ end
120
+
121
+ describe "#map_bool" do
122
+ let(:model_field) { :mod }
123
+ let(:public_field) { :pub }
124
+ it "creates the correct model_fields" do
125
+ proxy.map_bool(model_field, public_field)
126
+ expect(proxy.map.model_fields).to eql([model_field])
127
+ end
128
+ it "creates the correct public_fields" do
129
+ proxy.map_bool(model_field, public_field)
130
+ expect(proxy.map.public_fields).to eql([public_field])
131
+ end
132
+ it "creates the correct to_maps" do
133
+ proxy.map_bool(model_field, public_field)
134
+ expect(proxy.map.to_maps[0]).to contain_exactly(model_field, an_instance_of(Proc))
135
+ end
136
+ it "creates the correct from_maps" do
137
+ proxy.map_bool(model_field, public_field)
138
+ expect(proxy.map.from_maps[0]).to contain_exactly(public_field, an_instance_of(Proc))
139
+ end
140
+ [true, false, nil].each do |bool|
141
+ it "defines a to_map that converts models' #{bool} to public's #{bool}" do
142
+ proxy.map_bool(model_field, public_field)
143
+ expect(proxy.map.to_maps[0][1].call(bool)).to eql(public_field => bool)
144
+ end
145
+ end
146
+ ["true", "True", "t", "T", "yes", "Yes", "Y", "y", "1"].each do |truthy_string|
147
+ it "defines a from_map that converts public #{truthy_string} to model true" do
148
+ proxy.map_bool(model_field, public_field)
149
+ expect(proxy.map.from_maps[0][1].call(truthy_string)).to eql(model_field => true)
150
+ end
151
+ end
152
+ ["false", "False", "f", "F", "no", "No", "N", "n", "0"].each do |falsey_string|
153
+ it "defines a from_map that converts public #{falsey_string} to model false" do
154
+ proxy.map_bool(model_field, public_field)
155
+ expect(proxy.map.from_maps[0][1].call(falsey_string)).to eql(model_field => false)
156
+ end
157
+ end
158
+ [nil, "nil", "null"].each do |null|
159
+ it "defines a from_map that converts public #{null} to model nil" do
160
+ proxy.map_bool(model_field, public_field)
161
+ expect(proxy.map.from_maps[0][1].call(null)).to eql(model_field => nil)
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "#map_belongs_to" do
167
+ let(:model_field) { :user_id }
168
+ let(:public_field) { :user_name }
169
+ let(:options) { { model_class: User, sub_method: :name } }
170
+ let(:post) { Fabricate(:post, user: Fabricate(:user)) }
171
+ it "creates the correct model_fields" do
172
+ proxy.map_belongs_to(model_field, public_field, options)
173
+ expect(proxy.map.model_fields).to contain_exactly(model_field)
174
+ end
175
+ it "creates the correct public_fields" do
176
+ proxy.map_belongs_to(model_field, public_field, options)
177
+ expect(proxy.map.public_fields).to contain_exactly(public_field)
178
+ end
179
+ it "creates no simple_maps" do
180
+ proxy.map_belongs_to(model_field, public_field, options)
181
+ expect(proxy.map.simple_maps).to eql([])
182
+ end
183
+ it "creates the correct to_maps" do
184
+ proxy.map_belongs_to(model_field, public_field, options)
185
+ expect(proxy.map.to_maps[0]).to contain_exactly(model_field, an_instance_of(Proc))
186
+ end
187
+ it "creates the correct from_maps" do
188
+ proxy.map_belongs_to(model_field, public_field, options)
189
+ expect(proxy.map.from_maps[0]).to contain_exactly(public_field, an_instance_of(Proc))
190
+ end
191
+ it "defines a to_map that converts the model's parent to the public's field:value string" do
192
+ proxy.map_belongs_to(model_field, public_field, options)
193
+ expect(proxy.map.to_maps[0][1].call(post.user.id))
194
+ .to eql(public_field => post.user.name)
195
+ end
196
+ it "defines a from_map that converts the public's string to the model's owning model id" do
197
+ proxy.map_belongs_to(model_field, public_field, options)
198
+ expect(proxy.map.from_maps[0][1].call(post.user.name))
199
+ .to eql(model_field => post.user.id)
200
+ end
201
+ end
202
+
203
+ describe "#map_has_many" do
204
+ let(:model_field) { :posts }
205
+ let(:public_field) { :post_dates }
206
+ let(:options) { { model_class: Post, sub_method: :created_at } }
207
+ let(:user) { Fabricate(:user) }
208
+ before(:each) { Fabricate.times(2, :post, user: user) }
209
+ it "creates the correct model_fields" do
210
+ proxy.map_has_many(model_field, public_field, options)
211
+ expect(proxy.map.model_fields).to contain_exactly(model_field)
212
+ end
213
+ it "creates the correct public_fields" do
214
+ proxy.map_has_many(model_field, public_field, options)
215
+ expect(proxy.map.public_fields).to contain_exactly(public_field)
216
+ end
217
+ it "creates no simple_maps" do
218
+ proxy.map_has_many(model_field, public_field, options)
219
+ expect(proxy.map.simple_maps).to eql([])
220
+ end
221
+ it "creates the correct to_maps" do
222
+ proxy.map_has_many(model_field, public_field, options)
223
+ expect(proxy.map.to_maps[0]).to contain_exactly(model_field, an_instance_of(Proc))
224
+ end
225
+ it "creates the correct from_maps" do
226
+ proxy.map_has_many(model_field, public_field, options)
227
+ expect(proxy.map.from_maps[0]).to contain_exactly(public_field, an_instance_of(Proc))
228
+ end
229
+ it "defines a to_map that converts the model's children to the public's Array<String>" do
230
+ proxy.map_has_many(model_field, public_field, options)
231
+ expect(proxy.map.to_maps[0][1].call(user.posts))
232
+ .to eql(public_field => user.posts.pluck(:created_at))
233
+ end
234
+ it "defines a from_map that converts the public's string to the model's owning models" do
235
+ proxy.map_has_many(model_field, public_field, options)
236
+ expect(proxy.map.from_maps[0][1].call(user.posts.pluck(:created_at).to_a))
237
+ .to eql(model_field => user.posts.to_a)
238
+ end
239
+ end
240
+ end
241
+
242
+ end