populate-me 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a4e1b29c84ee80302c1e0dc749c0de5926191c1
4
- data.tar.gz: a600a41a3590d08d0a09f429fd65bded993f12cb
3
+ metadata.gz: c47e2bdae6fcc97d34ec729a23107c4d7af9069d
4
+ data.tar.gz: 60b764c3d31de7c5c5853a1d2eec72af7404e80e
5
5
  SHA512:
6
- metadata.gz: 22131a127100872095a3c1c5f8188f75a18bb5913c6d0611d4ad897a0013edaf09a0e1b9463384025d70e675933ca511b9f4135d4d4ce6f8c34123ae4db63aa0
7
- data.tar.gz: aac7ae50254aa787daaf7d83c10955a251483823cfc9f5f9d512777ecd95d2ba1ac134776cb7697c5b9b2d9a71451769e3faeaf14550af71b0f2f49de945da42
6
+ metadata.gz: 563cb8b28cce6d5364b0e7588b2feaadeb472facec7127de93d14b7a64a34b81a17e66b7d819bceee5f7f785b40cbc7e205ec213817532612295b2bbade91f99
7
+ data.tar.gz: cf7f5c53a887d2cee9b64aee30f85125588fe8f16cdf2edd358acf05eb6f39f12e0113c3e5759b951d9b96b10f8a60b18b00d659f500e914af6d93cc38493e5f
data/README.md CHANGED
@@ -15,12 +15,13 @@ Table of contents
15
15
  - [Table of contents](#table-of-contents)
16
16
  - [Documents](#documents)
17
17
  - [Schema](#schema)
18
- - [Validations](#validations)
19
18
  - [Relationships](#relationships)
19
+ - [Validations](#validations)
20
20
  - [Callbacks](#callbacks)
21
21
  - [Single Documents](#single-documents)
22
22
  - [Mongo documents](#mongo-documents)
23
23
  - [Admin](#admin)
24
+ - [Polymorphism](#polymorphism)
24
25
  - [Customize Admin](#customize-admin)
25
26
  - [API](#api)
26
27
 
@@ -67,6 +68,7 @@ itself, but here are the keys used by `PopulateMe`:
67
68
  - `:wrap` Set it to false if you do not want the form field to be wrapped in a `div` with a label.
68
69
  - `:default` Either a default value or a `proc` to run to get the default value.
69
70
  - `:required` Set to true if you want the field to be marked as required in the form.
71
+ - `:only_for` List of polymorphic type values
70
72
 
71
73
  As you can see, most of the options are made for you to tailor the form
72
74
  which `PopulateMe` will generate for you in the admin.
@@ -88,33 +90,6 @@ blog_article.published # Returns true or false
88
90
  blog_article.published = true
89
91
  ```
90
92
 
91
- ### Validations
92
-
93
- In its simplest form, validations are done by overriding the `#validate` method and declaring errors with the `#error_on` method.
94
-
95
- ```ruby
96
- class Person < PopulateMe::Document
97
-
98
- field :name
99
-
100
- def validate
101
- error_on(:name, 'Cannot be fake') if self.name=='John Doe'
102
- end
103
-
104
- end
105
- ```
106
-
107
- If you don't use the `PopulateMe` interface and create a document
108
- programmatically, here is what it could look like:
109
-
110
- ```ruby
111
- person = Person.new(name: 'John Doe')
112
- person.new? # returns true
113
- person.save # fails
114
- person.valid? # returns false
115
- person.errors # returns { name: ['Cannot be fake'] }
116
- ```
117
-
118
93
  ### Relationships
119
94
 
120
95
  In its simplest form, when using the modules convention, relationships
@@ -151,6 +126,33 @@ It uses the `PopulateMe::Document::admin_find` and
151
126
  `PopulateMe::Document::admin_find_first` methods in the background,
152
127
  so default sorting order is respected.
153
128
 
129
+ ### Validations
130
+
131
+ In its simplest form, validations are done by overriding the `#validate` method and declaring errors with the `#error_on` method.
132
+
133
+ ```ruby
134
+ class Person < PopulateMe::Document
135
+
136
+ field :name
137
+
138
+ def validate
139
+ error_on(:name, 'Cannot be fake') if self.name=='John Doe'
140
+ end
141
+
142
+ end
143
+ ```
144
+
145
+ If you don't use the `PopulateMe` interface and create a document
146
+ programmatically, here is what it could look like:
147
+
148
+ ```ruby
149
+ person = Person.new(name: 'John Doe')
150
+ person.new? # returns true
151
+ person.save # fails
152
+ person.valid? # returns false
153
+ person.errors # returns { name: ['Cannot be fake'] }
154
+ ```
155
+
154
156
  ### Callbacks
155
157
 
156
158
  There are some classic hooks which trigger the callbacks you declare.
@@ -404,6 +406,131 @@ probably for [single documents](#single-documents) because they are not part of
404
406
  a list. The ID would then be litterally `unique`, or whatever ID you declared
405
407
  instead.
406
408
 
409
+ ### Polymorphism
410
+
411
+ You can use the schema to set a Document class as polymorphic. The consequence
412
+ is that the admin will make you choose a type before creating a new document.
413
+ And then the form will only display the fields applicable to this polymorphic
414
+ type. And once created, it will only show relationships applicable to its
415
+ polymorphic type. You can do this with the `:only_for` option.
416
+
417
+ Here is an example of a document that can either be a title with a paragraph, or
418
+ a title with a set of images:
419
+
420
+ ```ruby
421
+ # lib/models/box.rb
422
+
423
+ require 'populate_me/document'
424
+
425
+ class Box < PopulateMe::Document
426
+
427
+ field :title
428
+ field :paragraph, type: :text, only_for: 'Paragraph'
429
+ relationship :images, only_for: 'Image slider'
430
+ position_field
431
+
432
+ end
433
+ ```
434
+
435
+ In this case, when you create a `Box` with the polymorphic type `Paragraph`, the
436
+ form will have a field for `:paragraph` but no relationship for images. And if
437
+ you create a `Box` with the polymorphic type `Image slider`, it will be the
438
+ opposite.
439
+
440
+ The option `:only_for` can also be an `Array`. Actually, when inspecting the
441
+ `fields`, you'll see that even when you pass a `String`, it will be put inside
442
+ an `Array`.
443
+
444
+ ```ruby
445
+ Box.fields[:paragraph][:only_for] # => ['Paragraph']
446
+ ```
447
+
448
+ A hidden field is automatically created called `:polymorphic_type`, therefore
449
+ it is a method you can call to get or set the `:polymorphic_type`.
450
+
451
+ ```ruby
452
+ box = Box.new polymorphic_type: 'Paragraph'
453
+ box.polymorphic_type # => 'Paragraph'
454
+ ```
455
+
456
+ One of the information that the field contains is all the `:values` the field
457
+ can have.
458
+
459
+ ```ruby
460
+ Box.fields[:polymorphic_type][:values] # => ['Paragraph', 'Image slider']
461
+ ```
462
+
463
+ They are in the order they are declared in the fields. If you want to just set
464
+ this list yourself or any other option attached to the `:polimorphic_type` field
465
+ you can do so with the `Document::polymorphic` class method.
466
+
467
+ ```ruby
468
+ # lib/models/box.rb
469
+
470
+ require 'populate_me/document'
471
+
472
+ class Box < PopulateMe::Document
473
+
474
+ polymorphic values: ['Image slider', 'Paragraph']
475
+ field :title
476
+ field :paragraph, type: :text, only_for: 'Paragraph'
477
+ relationship :images, only_for: 'Image slider'
478
+ position_field
479
+
480
+ end
481
+ ```
482
+
483
+ If each polymorphic type has a lot of fields and/or relationships, you can use the
484
+ `Document::only_for` class method which sets the `:only_for` option for
485
+ everything inside the block.
486
+
487
+ ```ruby
488
+ # lib/models/media.rb
489
+
490
+ require 'populate_me/document'
491
+
492
+ class Media < PopulateMe::Document
493
+
494
+ field :title
495
+ only_for 'Book' do
496
+ field :author
497
+ field :publisher
498
+ relationship :chapters
499
+ end
500
+ only_for 'Movie' do
501
+ field :script_writer
502
+ field :director
503
+ relationship :scenes
504
+ relationship :actors
505
+ end
506
+ position_field
507
+
508
+ end
509
+ ```
510
+
511
+ It is worth noting that this implementation of polymorphism is supposed to work
512
+ with fixed schema databases, and therefore all fields and relationship exist for
513
+ each document. In our case, books would still have a `#director` method. The
514
+ difference is only cosmetic and mainly allows you to have forms that are less
515
+ crowded in the admin.
516
+
517
+ To mitigate this, a few methods are there to help you. There is a predicate for
518
+ knowing if a class is polymorphic.
519
+
520
+ ```ruby
521
+ Media.polymorphic? # => true
522
+ ```
523
+
524
+ For each document, you can inspect its polymorphic type or check if a field or
525
+ relationship is applicable.
526
+
527
+ ```ruby
528
+ book = Media.new polymorphic_type: 'Book', title: 'Hot Water Music', author: 'Charles Bukowski'
529
+ book.polymorphic_type # => 'Book'
530
+ book.field_applicable? :author # => true
531
+ book.relationship_applicable? :actors # => false
532
+ ```
533
+
407
534
  ### Customize Admin
408
535
 
409
536
  You can customize the admin with a few settings.
@@ -65,11 +65,22 @@ class Article < PopulateMe::Document
65
65
  field :price, type: :price
66
66
  position_field
67
67
 
68
+ relationship :sections
69
+
68
70
  after :save do
69
71
  puts self.inspect
70
72
  end
71
73
  end
72
74
 
75
+ class Article::Section < PopulateMe::Document
76
+
77
+ field :short, only_for: 'Nice Short'
78
+ field :long, only_for: 'Nice Long'
79
+ field :article_id, type: :hidden
80
+ position_field scope: :article_id
81
+
82
+ end
83
+
73
84
  # Admin ##########
74
85
 
75
86
  require "populate_me/admin"
@@ -60,6 +60,10 @@ button { cursor: pointer; }
60
60
 
61
61
  /* Lists */
62
62
 
63
+ .new-document-btn-wrap > * {
64
+ vertical-align: middle;
65
+ }
66
+
63
67
  .new-document-btn, .new-nested-document-btn {
64
68
  border: 0;
65
69
  font-size: 1.615em;
@@ -30,6 +30,19 @@ PopulateMe.display_file_size = function(size) {
30
30
  return size+unit;
31
31
  };
32
32
 
33
+ // Add or update query string parameter on a URI
34
+ PopulateMe.update_query_parameter = function(uri, key, value) {
35
+ var sane_key = key.replace(/[\[\]]/g, "\\$&");
36
+ var re = new RegExp("([?&])" + sane_key + "=.*?(&|$)", "i");
37
+ var separator = uri.indexOf('?') !== -1 ? "&" : "?";
38
+ if (uri.match(re)) {
39
+ return uri.replace(re, '$1' + key + "=" + encodeURIComponent(value) + '$2');
40
+ }
41
+ else {
42
+ return uri + separator + key + "=" + encodeURIComponent(value);
43
+ }
44
+ };
45
+
33
46
  // Template helpers
34
47
  PopulateMe.template_helpers = {
35
48
  custom_partial_or_default: function() {
@@ -180,7 +193,14 @@ PopulateMe.init_column = function(c) {
180
193
  $('textarea',c).trigger('input');
181
194
 
182
195
  // Init multiple select with asmSelect
183
- $('select[multiple]').asmSelect({ sortable: true, removeLabel: '&times;' });
196
+ $('select[multiple]', c).asmSelect({ sortable: true, removeLabel: '&times;' });
197
+
198
+ // Polymorphic selector
199
+ $('select.polymorphic_type_values').change(function() {
200
+ var $this = $(this);
201
+ var link = $this.next();
202
+ link.attr('href', PopulateMe.update_query_parameter(link.attr('href'), 'data[polymorphic_type]', $this.val()));
203
+ }).change();
184
204
 
185
205
  // Possible callback from custom javascript file.
186
206
  // If you need to do something on init_column,
@@ -36,6 +36,14 @@
36
36
  {{page_title}}
37
37
  </h1>
38
38
  <p class='new-document-btn-wrap'>
39
+ {{#is_polymorphic}}
40
+ <select class='polymorphic_type_values'>
41
+ {{#polymorphic_type_values}}
42
+ <option value='{{.}}'>{{.}}</option>
43
+ {{/polymorphic_type_values}}
44
+ </select>
45
+ &nbsp;
46
+ {{/is_polymorphic}}
39
47
  <a href="<%= request.script_name %>/form/{{dasherized_class_name}}{{#new_data}}?{{new_data}}{{/new_data}}" class='column-push new-document-btn' title='Create'>+</a>
40
48
  </p>
41
49
  <ol class='documents' data-sort-field='{{sort_field}}' data-sort-url='<%= request.script_name %>/api/{{dasherized_class_name}}'>
@@ -65,6 +73,9 @@
65
73
 
66
74
  <script id="template-form" type="x-tmpl-mustache">
67
75
  <h1>{{page_title}}</h1>
76
+ {{#polymorphic_type}}
77
+ <p>({{polymorphic_type}})</p>
78
+ {{/polymorphic_type}}
68
79
  <form action="<%= request.script_name %>/api/{{admin_url}}" method="POST" accept-charset="utf-8" class='admin-{{#is_new}}post{{/is_new}}{{^is_new}}put{{/is_new}}'>
69
80
  {{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
70
81
  {{^is_new}}
@@ -88,9 +88,10 @@ module PopulateMe
88
88
  end
89
89
 
90
90
  def to_s
91
- return inspect if self.class.label_field.nil?
92
- me = self.__send__(self.class.label_field)
93
- WebUtils.blank?(me) ? inspect : me
91
+ default = "#{self.class}#{' ' unless WebUtils.blank?(self.id)}#{self.id}"
92
+ return default if self.class.label_field.nil?
93
+ me = self.__send__(self.class.label_field).dup
94
+ WebUtils.blank?(me) ? default : me
94
95
  end
95
96
 
96
97
  def new?; self._is_new; end
@@ -20,14 +20,14 @@ module PopulateMe
20
20
  title: WebUtils.truncate(to_s, 60),
21
21
  image_url: admin_image_url,
22
22
  local_menu: self.class.relationships.inject([]) do |out,(k,v)|
23
- unless v[:hidden]
23
+ if not v[:hidden] and self.relationship_applicable?(k)
24
24
  out << {
25
25
  title: "#{v[:label]}",
26
26
  href: "#{o[:request].script_name}/list/#{WebUtils.dasherize_class_name(v[:class_name])}?filter[#{v[:foreign_key]}]=#{self.id}",
27
27
  new_page: false
28
28
  }
29
- out
30
29
  end
30
+ out
31
31
  end
32
32
  }
33
33
  end
@@ -41,16 +41,19 @@ module PopulateMe
41
41
  }
42
42
  self.class.complete_field_options :_class, class_item
43
43
  items = self.class.fields.inject([class_item]) do |out,(k,item)|
44
- if item[:form_field]
44
+ if item[:form_field] and self.field_applicable?(k)
45
45
  out << outcast(k, item, o)
46
46
  end
47
47
  out
48
48
  end
49
+ page_title = self.new? ? "New #{self.class.to_s_short}" : self.to_s
50
+ # page_title << " (#{self.polymorphic_type})" if self.class.polymorphic?
49
51
  {
50
52
  template: "template#{'_nested' if o[:nested]}_form",
51
- page_title: self.new? ? "New #{self.class.to_s_short}" : self.to_s,
53
+ page_title: page_title,
52
54
  admin_url: self.to_admin_url,
53
55
  is_new: self.new?,
56
+ polymorphic_type: self.class.polymorphic? ? self.polymorphic_type : nil,
54
57
  fields: items
55
58
  }
56
59
  end
@@ -121,6 +124,8 @@ module PopulateMe
121
124
  page_title: self.to_s_short_plural,
122
125
  dasherized_class_name: WebUtils.dasherize_class_name(self.name),
123
126
  new_data: new_data,
127
+ is_polymorphic: self.polymorphic?,
128
+ polymorphic_type_values: self.polymorphic? ? self.fields[:polymorphic_type][:values] : nil,
124
129
  sort_field: self.sort_field_for(o),
125
130
  # 'command_plus'=> !self.populate_config[:no_plus],
126
131
  # 'command_search'=> !self.populate_config[:no_search],
@@ -14,7 +14,7 @@ module PopulateMe
14
14
  def fields; @fields ||= {}; end
15
15
 
16
16
  def field name, o={}
17
- set_id_field if self.fields.empty?&&o[:type]!=:id
17
+ set_id_field if self.fields.empty? and o[:type]!=:id
18
18
  complete_field_options name, o
19
19
  if o[:type]==:list
20
20
  define_method(name) do
@@ -34,6 +34,9 @@ module PopulateMe
34
34
  o[:wrap] = false unless o[:form_field]
35
35
  WebUtils.ensure_key! o, :wrap, ![:hidden,:list].include?(o[:type])
36
36
  WebUtils.ensure_key! o, :label, WebUtils.label_for_field(name)
37
+ unless [:id, :polymorphic_type].include?(o[:type])
38
+ complete_only_for_field_option o
39
+ end
37
40
  if o[:type]==:attachment
38
41
  WebUtils.ensure_key! o, :class_name, settings.default_attachment_class
39
42
  raise MissingAttachmentClassError, "No attachment class was provided for the #{self.name} field: #{name}" if o[:class_name].nil?
@@ -51,6 +54,20 @@ module PopulateMe
51
54
  end
52
55
  end
53
56
 
57
+ def complete_only_for_field_option o={}
58
+ if @currently_only_for
59
+ o[:only_for] = @currently_only_for
60
+ end
61
+ if o[:only_for].is_a?(String)
62
+ o[:only_for] = [o[:only_for]]
63
+ end
64
+ if o.key?(:only_for)
65
+ self.polymorphic unless self.polymorphic?
66
+ self.fields[:polymorphic_type][:values] += o[:only_for]
67
+ self.fields[:polymorphic_type][:values].uniq!
68
+ end
69
+ end
70
+
54
71
  def set_id_field
55
72
  field :id, {type: :id}
56
73
  end
@@ -62,12 +79,51 @@ module PopulateMe
62
79
  sort_by name, (o[:direction]||:asc)
63
80
  end
64
81
 
82
+ def polymorphic o={}
83
+ WebUtils.ensure_key! o, :type, :polymorphic_type
84
+ WebUtils.ensure_key! o, :values, []
85
+ WebUtils.ensure_key! o, :wrap, false
86
+ WebUtils.ensure_key! o, :input_attributes, {}
87
+ WebUtils.ensure_key! o[:input_attributes], :type, :hidden
88
+ field(:polymorphic_type, o) unless self.polymorphic?
89
+ end
90
+
91
+ def only_for polymorphic_type_values, &bloc
92
+ @currently_only_for = polymorphic_type_values
93
+ yield if block_given?
94
+ remove_instance_variable(:@currently_only_for)
95
+ end
96
+
97
+ def polymorphic?
98
+ self.fields.key? :polymorphic_type
99
+ end
100
+
101
+ def field_applicable? f, p_type=nil
102
+ applicable? :fields, f, p_type
103
+ end
104
+
105
+ def relationship_applicable? f, p_type=nil
106
+ applicable? :relationships, f, p_type
107
+ end
108
+
109
+ def applicable? target, f, p_type=nil
110
+ return false unless self.__send__(target).key?(f)
111
+ return true unless self.polymorphic?
112
+ return true unless self.__send__(target)[f].key?(:only_for)
113
+ return true if p_type.nil?
114
+ self.__send__(target)[f][:only_for].include? p_type
115
+ end
116
+ private :applicable?
117
+
65
118
  def label sym # sets the label_field
66
119
  @label_field = sym.to_sym
67
120
  end
68
-
121
+
69
122
  def label_field
70
- @label_field || self.fields.keys[1]
123
+ return @label_field if self.fields.empty?
124
+ @label_field || self.fields.find do |k,v|
125
+ not [:id,:polymorphic_type].include?(v[:type])
126
+ end[0]
71
127
  end
72
128
 
73
129
  def sort_by f, direction=:asc
@@ -88,6 +144,7 @@ module PopulateMe
88
144
  WebUtils.ensure_key! o, :foreign_key, "#{WebUtils.dasherize_class_name(self.name).gsub('-','_')}_id"
89
145
  o[:foreign_key] = o[:foreign_key].to_sym
90
146
  WebUtils.ensure_key! o, :dependent, true
147
+ complete_only_for_field_option o
91
148
  self.relationships[name] = o
92
149
 
93
150
  define_method(name) do
@@ -116,6 +173,18 @@ module PopulateMe
116
173
 
117
174
  end
118
175
 
176
+ # Instance methods
177
+
178
+ def field_applicable? f
179
+ p_type = self.class.polymorphic? ? self.polymorphic_type : nil
180
+ self.class.field_applicable? f, p_type
181
+ end
182
+
183
+ def relationship_applicable? f
184
+ p_type = self.class.polymorphic? ? self.polymorphic_type : nil
185
+ self.class.relationship_applicable? f, p_type
186
+ end
187
+
119
188
  end
120
189
  end
121
190
  end
@@ -1,4 +1,4 @@
1
1
  module PopulateMe
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.0'
3
3
  end
4
4
 
@@ -24,10 +24,9 @@ describe PopulateMe::Document do
24
24
  end
25
25
 
26
26
  describe '#to_s' do
27
- it 'Delegates to #inspect' do
28
- subject.stub :inspect, "inspection" do
29
- assert_equal "inspection", subject.to_s
30
- end
27
+ it 'Defaults to class name and ID' do
28
+ obj = subject_class.new id: '42'
29
+ assert_equal "#{subject_class} 42", obj.to_s
31
30
  end
32
31
  describe "Has a label field" do
33
32
  describe "And the field is not blank" do
@@ -38,17 +37,26 @@ describe PopulateMe::Document do
38
37
  end
39
38
  end
40
39
  end
40
+ it 'Does not pass a reference that can be modified' do
41
+ subject_class.stub :label_field, :my_label_field do
42
+ subject.stub :my_label_field, 'my label' do
43
+ assert_equal 'my label', subject.to_s
44
+ var = subject.to_s
45
+ var << 'BOOM'
46
+ assert_equal 'my label', subject.to_s
47
+ end
48
+ end
49
+ end
41
50
  end
42
51
  describe "And the field is blank" do
43
- it "Delegates to the #inspect" do
52
+ it 'Defaults to class name and ID' do
44
53
  subject_class.stub :label_field, :my_label_field do
45
- subject.stub :inspect, "inspection" do
46
- subject.stub :my_label_field, '' do
47
- assert_equal 'inspection', subject.to_s
48
- end
49
- subject.stub :my_label_field, nil do
50
- assert_equal 'inspection', subject.to_s
51
- end
54
+ obj = subject_class.new id: '42'
55
+ subject.stub :my_label_field, '' do
56
+ assert_equal "#{subject_class} 42", obj.to_s
57
+ end
58
+ subject.stub :my_label_field, nil do
59
+ assert_equal "#{subject_class} 42", obj.to_s
52
60
  end
53
61
  end
54
62
  end
@@ -41,10 +41,29 @@ describe PopulateMe::Document, 'AdminAdapter' do
41
41
  end
42
42
  end
43
43
 
44
+ describe '::to_admin_list' do
45
+ class PolyAdapted < PopulateMe::Document
46
+ polymorphic values: ['Shape 1', 'Shape 2']
47
+ end
48
+ class NotPolyAdapted < PopulateMe::Document
49
+ end
50
+ it 'Contains polymorphic_type values and predicate if polymorphic' do
51
+ assert PolyAdapted.to_admin_list[:is_polymorphic]
52
+ assert_equal ['Shape 1', 'Shape 2'], PolyAdapted.to_admin_list[:polymorphic_type_values]
53
+ refute NotPolyAdapted.to_admin_list[:is_polymorphic]
54
+ assert_nil NotPolyAdapted.to_admin_list[:polymorphic_type_values]
55
+ end
56
+ end
57
+
44
58
  describe '#to_admin_list_item' do
45
59
  class ContentTitle < PopulateMe::Document
46
60
  field :content
47
61
  end
62
+ class PolyListItem < PopulateMe::Document
63
+ field :name
64
+ relationship :images, only_for: 'Slider'
65
+ relationship :paragraphs, only_for: 'Chapter'
66
+ end
48
67
  it 'Sets ID as a string version' do
49
68
  doc = ContentTitle.new id: 3
50
69
  assert_equal '3', doc.to_admin_list_item[:id]
@@ -67,6 +86,14 @@ describe PopulateMe::Document, 'AdminAdapter' do
67
86
  assert_equal doc.content, title
68
87
  end
69
88
  end
89
+ describe 'Polymorphism' do
90
+ it 'Only keeps in the local menu applicable relationships' do
91
+ doc = PolyListItem.new(polymorphic_type: 'Slider')
92
+ list_item = doc.to_admin_list_item request: Struct.new(:script_name).new('/admin')
93
+ assert_equal 1, list_item[:local_menu].size
94
+ assert_equal 'Images', list_item[:local_menu][0][:title]
95
+ end
96
+ end
70
97
  end
71
98
 
72
99
  describe '::admin_find ::admin_find_first' do
@@ -148,4 +175,47 @@ describe PopulateMe::Document, 'AdminAdapter' do
148
175
 
149
176
  end
150
177
 
178
+ describe '#to_admin_form' do
179
+ class PolyForm < PopulateMe::Document
180
+ field :name
181
+ field :image, only_for: 'Image'
182
+ field :title, only_for: 'Article'
183
+ field :content, only_for: 'Article'
184
+ field :position
185
+ end
186
+ class NotPolyForm < PopulateMe::Document
187
+ field :name
188
+ end
189
+ it 'Sets page_title according to label_field or class name when new' do
190
+ obj = NotPolyForm.new
191
+ form = obj.to_admin_form
192
+ assert_equal 'New Not Poly Form', form[:page_title]
193
+ obj = NotPolyForm.new name: "No Poly"
194
+ obj._is_new = false
195
+ form = obj.to_admin_form
196
+ assert_equal 'No Poly', form[:page_title]
197
+ end
198
+ it 'Only has fields for the current polymorphic type' do
199
+ obj = PolyForm.new polymorphic_type: 'Article'
200
+ form = obj.to_admin_form
201
+ assert_nil form[:fields].find{|f| f[:field_name]==:image}
202
+ refute_nil form[:fields].find{|f| f[:field_name]==:title}
203
+ refute_nil form[:fields].find{|f| f[:field_name]==:content}
204
+ end
205
+ it 'Works when not polymorphic' do
206
+ obj = NotPolyForm.new
207
+ form = obj.to_admin_form
208
+ refute_nil form[:fields].find{|f| f[:field_name]==:name}
209
+ end
210
+ it 'Includes the polymorphic_type at top level' do
211
+ obj = PolyForm.new polymorphic_type: 'Article'
212
+ form = obj.to_admin_form
213
+ assert_equal 'Article', form[:polymorphic_type]
214
+ obj = PolyForm.new
215
+ form = obj.to_admin_form
216
+ assert_nil form[:polymorphic_type]
217
+ end
218
+ end
219
+
151
220
  end
221
+
@@ -119,5 +119,144 @@ describe PopulateMe::Document, 'Schema' do
119
119
 
120
120
  end
121
121
 
122
+ describe 'Polymorphism' do
123
+
124
+ class PolyBox < PopulateMe::Document
125
+ field :first_name
126
+ field :last_name
127
+ field :middle_name, only_for: ['Middle', 'Long name']
128
+ field :nick_name, only_for: 'Funny'
129
+ field :third_name, only_for: 'Long name'
130
+ end
131
+
132
+ class NotPoly < PopulateMe::Document
133
+ field :name
134
+ relationship :images
135
+ end
136
+
137
+ class JustPoly < PopulateMe::Document
138
+ polymorphic
139
+ end
140
+
141
+ class PolyCustom < PopulateMe::Document
142
+ polymorphic type: :text, custom_option: 'Custom'
143
+ end
144
+
145
+ class PolyApplicable < PopulateMe::Document
146
+ field :name_1, only_for: 'Shape 1'
147
+ field :name_2, only_for: 'Shape 2'
148
+ field :position
149
+ end
150
+
151
+ class PolyLabel < PopulateMe::Document
152
+ polymorphic
153
+ field :name
154
+ end
155
+
156
+ class PolyRelationship < PopulateMe::Document
157
+ field :name
158
+ field :short_description, only_for: 'Chapter'
159
+ relationship :images, only_for: 'Slider'
160
+ relationship :paragraphs, only_for: 'Chapter'
161
+ end
162
+
163
+ class PolyGroup < PopulateMe::Document
164
+ only_for 'Slider' do
165
+ field :name
166
+ relationship :images
167
+ end
168
+ field :no_only_for
169
+ only_for ['Try','This'] do
170
+ field :crazy
171
+ end
172
+ field :position
173
+ end
174
+
175
+ it 'Creates a field for polymorphic type if it does not exist yet' do
176
+ assert_equal :polymorphic_type, PolyBox.fields[:polymorphic_type][:type]
177
+ assert_equal :polymorphic_type, JustPoly.fields[:polymorphic_type][:type]
178
+ assert_equal :polymorphic_type, PolyRelationship.fields[:polymorphic_type][:type]
179
+ end
180
+
181
+ it 'By default polymorphic_type is hidden and not wrapped' do
182
+ assert_equal :hidden, PolyBox.fields[:polymorphic_type][:input_attributes][:type]
183
+ refute PolyBox.fields[:polymorphic_type][:wrap]
184
+ end
185
+
186
+ it 'Does not create polymorphic type field if not required' do
187
+ assert_nil NotPoly.fields[:polymorphic_type]
188
+ end
189
+
190
+ it 'Gathers all polymorphic type unique values' do
191
+ assert_equal ['Middle', 'Long name', 'Funny'], PolyBox.fields[:polymorphic_type][:values]
192
+ assert_equal [], JustPoly.fields[:polymorphic_type][:values]
193
+ assert_equal ['Chapter', 'Slider'], PolyRelationship.fields[:polymorphic_type][:values]
194
+ end
195
+
196
+ it 'Adds or update field options for polymorphic type passed as arguments' do
197
+ assert_equal :text, PolyCustom.fields[:polymorphic_type][:type]
198
+ assert_equal 'Custom', PolyCustom.fields[:polymorphic_type][:custom_option]
199
+ end
200
+
201
+ it 'Forces only_for field option to be an Array if String' do
202
+ assert_nil PolyBox.fields[:first_name][:only_for]
203
+ assert_equal ['Middle', 'Long name'], PolyBox.fields[:middle_name][:only_for]
204
+ assert_equal ['Funny'], PolyBox.fields[:nick_name][:only_for]
205
+ assert_equal ['Slider'], PolyRelationship.relationships[:images][:only_for]
206
+ end
207
+
208
+ it 'Has a polymorphic? predicate' do
209
+ assert PolyBox.polymorphic?
210
+ refute NotPoly.polymorphic?
211
+ end
212
+
213
+ it 'Knows when a field is applicable to a polymorphic_type' do
214
+ assert NotPoly.field_applicable?(:name, nil) # Not polymorphic
215
+ assert NotPoly.field_applicable?(:name, 'Fake') # Not polymorphic
216
+ refute NotPoly.field_applicable?(:non_existing_field, nil) # Not polymorphic
217
+ assert PolyApplicable.field_applicable?(:polymorphic_type, 'Shape 1') # no only_for
218
+ assert PolyApplicable.field_applicable?(:position, 'Shape 1') # no only_for
219
+ refute PolyApplicable.field_applicable?(:name_2, 'Shape 1') # not included
220
+ assert PolyApplicable.field_applicable?(:name_2, nil) # no type set yet
221
+
222
+ assert NotPoly.new.field_applicable?(:name) # Not polymorphic
223
+ refute NotPoly.new.field_applicable?(:non_existing_field) # Not polymorphic
224
+ assert PolyApplicable.new(polymorphic_type: 'Shape 2').field_applicable?(:name_2)
225
+ refute PolyApplicable.new(polymorphic_type: 'Shape 2').field_applicable?(:name_1)
226
+ assert PolyApplicable.new.field_applicable?(:name_2) # No type set yet
227
+ assert PolyApplicable.new.field_applicable?(:name_1) # No type set yes
228
+ end
229
+
230
+ it 'Knows when a relationship is applicable to a polymorphic_type' do
231
+ assert PolyRelationship.relationship_applicable?(:images, 'Slider')
232
+ refute PolyRelationship.relationship_applicable?(:images, 'Chapter')
233
+ assert PolyRelationship.new(polymorphic_type: 'Slider').relationship_applicable?(:images)
234
+ refute PolyRelationship.new(polymorphic_type: 'Chapter').relationship_applicable?(:images)
235
+ end
236
+
237
+ it 'Ignores polymorphic_type when picking the default label_field' do
238
+ assert_equal :name, PolyLabel.label_field
239
+ end
240
+
241
+ it 'Uses groups to define only_for option for all included fields and relationships' do
242
+ assert_equal :polymorphic_type, PolyGroup.fields[:polymorphic_type][:type]
243
+ assert_equal ['Slider', 'Try', 'This'], PolyGroup.fields[:polymorphic_type][:values]
244
+ assert_equal ['Slider'], PolyGroup.fields[:name][:only_for]
245
+ assert_equal ['Slider'], PolyGroup.relationships[:images][:only_for]
246
+ assert_equal ['Try','This'], PolyGroup.fields[:crazy][:only_for]
247
+ end
248
+
249
+ it 'Group only_for option does not leak on special fields' do
250
+ refute PolyGroup.fields[:id].key?(:only_for)
251
+ refute PolyGroup.fields[:polymorphic_type].key?(:only_for)
252
+ end
253
+
254
+ it 'Group only_for option does not leak outside of their scope' do
255
+ refute PolyGroup.fields[:no_only_for].key?(:only_for)
256
+ refute PolyGroup.fields[:position].key?(:only_for)
257
+ end
258
+
259
+ end
260
+
122
261
  end
123
262
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: populate-me
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mickael Riga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-10 00:00:00.000000000 Z
11
+ date: 2018-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: web-utils