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 +4 -4
- data/README.md +155 -28
- data/example/config.ru +11 -0
- data/lib/populate_me/admin/__assets__/css/main.css +4 -0
- data/lib/populate_me/admin/__assets__/js/main.js +21 -1
- data/lib/populate_me/admin/views/page.erb +11 -0
- data/lib/populate_me/document.rb +4 -3
- data/lib/populate_me/document_mixins/admin_adapter.rb +9 -4
- data/lib/populate_me/document_mixins/schema.rb +72 -3
- data/lib/populate_me/version.rb +1 -1
- data/test/test_document.rb +20 -12
- data/test/test_document_admin_adapter.rb +70 -0
- data/test/test_document_schema.rb +139 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c47e2bdae6fcc97d34ec729a23107c4d7af9069d
|
4
|
+
data.tar.gz: 60b764c3d31de7c5c5853a1d2eec72af7404e80e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/example/config.ru
CHANGED
@@ -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"
|
@@ -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: '×' });
|
196
|
+
$('select[multiple]', c).asmSelect({ sortable: true, removeLabel: '×' });
|
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
|
+
|
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}}
|
data/lib/populate_me/document.rb
CHANGED
@@ -88,9 +88,10 @@ module PopulateMe
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def to_s
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
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:
|
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
|
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
|
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
|
data/lib/populate_me/version.rb
CHANGED
data/test/test_document.rb
CHANGED
@@ -24,10 +24,9 @@ describe PopulateMe::Document do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
describe '#to_s' do
|
27
|
-
it '
|
28
|
-
|
29
|
-
|
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
|
52
|
+
it 'Defaults to class name and ID' do
|
44
53
|
subject_class.stub :label_field, :my_label_field do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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.
|
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-
|
11
|
+
date: 2018-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: web-utils
|