rails_view_adapters 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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