populate-me 0.7.1 → 0.8.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 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