contents_core 0.2.5 → 0.3.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/MIT-LICENSE +1 -1
- data/README.md +86 -24
- data/Rakefile +0 -1
- data/app/models/contents_core/block.rb +97 -61
- data/app/models/contents_core/item.rb +55 -12
- data/app/models/contents_core/item_array.rb +5 -1
- data/app/models/contents_core/item_boolean.rb +1 -1
- data/app/models/contents_core/item_datetime.rb +1 -1
- data/app/models/contents_core/item_file.rb +2 -2
- data/app/models/contents_core/item_float.rb +1 -1
- data/app/models/contents_core/item_hash.rb +1 -1
- data/app/models/contents_core/item_integer.rb +1 -1
- data/app/models/contents_core/item_object.rb +9 -3
- data/app/models/contents_core/item_string.rb +9 -9
- data/app/models/contents_core/item_text.rb +1 -1
- data/config/initializers/contents_core.rb +26 -21
- data/db/migrate/20170414173603_create_contents_core_blocks.rb +10 -10
- data/db/migrate/20170414173610_create_contents_core_items.rb +12 -3
- data/lib/contents_core.rb +32 -4
- data/lib/contents_core/blocks.rb +10 -2
- data/lib/contents_core/version.rb +1 -1
- metadata +28 -15
- data/db/migrate/20170414173611_add_extra_items.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33b635a47b2e5a090eea2d2c99dba30eef64c508
|
4
|
+
data.tar.gz: 93e8aa348c86bc228f0b0572a7fbdbec58fe5ad4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb9f8914cbd38b7d91ae103c317c8cbc6310153f2a35d9b294d98c5abfcc84ee919cbbd406e89590ab935bf23cc82dab254e4ed315734c63350957cd0887594e
|
7
|
+
data.tar.gz: 3b341cdfe78f537cf7118fedc018d49bf834d4677fd1358cecdc2522bef8747411e951e92adee917df5556cadc04c8fb294ef0d31e35d5104513484e2fe74db5
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# ContentsCore [](https://badge.fury.io/rb/contents_core) [](https://travis-ci.org/blocknotes/contents_core)
|
2
2
|
|
3
|
-
A Rails gem which offer a
|
3
|
+
A Rails gem which offer a structure to manage contents in a flexible way: blocks with recursive nested blocks + items as "leaves"
|
4
4
|
|
5
5
|
Disclaimer: this component is in ALPHA, major changes could happen
|
6
6
|
|
7
7
|
Goals:
|
8
8
|
- attach the contents structure to a model transparently
|
9
|
-
- improve block views management
|
10
9
|
- add fields to blocks without migrations
|
10
|
+
- offer helpers to render blocks in views
|
11
|
+
- cache-ready
|
11
12
|
|
12
|
-
|
13
|
+
## Install
|
13
14
|
|
14
15
|
- Add to the Gemfile:
|
15
16
|
`gem 'contents_core'`
|
@@ -17,24 +18,85 @@ Goals:
|
|
17
18
|
`rails contents_core:install:migrations`
|
18
19
|
- Execute migrations
|
19
20
|
- Add the concern *Blocks* to your model (ex. *Page*): `include ContentsCore::Blocks`
|
20
|
-
-
|
21
|
+
- Optionally add the blocks to a view (ex. *page show*): `= render partial: 'contents_core/blocks', locals: { container: @page }`
|
21
22
|
|
22
|
-
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Working with blocks/items
|
26
|
+
|
27
|
+
- Basic operations (example parent model: *Page*):
|
28
|
+
```ruby
|
29
|
+
page = Page.first
|
30
|
+
page.create_block :slider, name: 'a-slider', create_children: 3 # Create a silder with 3 slides
|
31
|
+
page.current_blocks.map{ |block| block.name } # current_blocks -> all published ordered blocks and query cached
|
32
|
+
block = page.get_block 'a-slider'
|
33
|
+
block.tree # list all items of a block
|
34
|
+
block.get 'slide-2.title' # get value of 'title' field of sub block with name 'slide-2' (name automatically generated at creation)
|
35
|
+
block.set 'slide-2.title' # set field value
|
36
|
+
block.save
|
37
|
+
```
|
38
|
+
|
39
|
+
- Other operations:
|
40
|
+
```ruby
|
41
|
+
block = ContentsCore::Block.last
|
42
|
+
ContentsCore.create_block_in_parent block, :text # create a sub block in a block
|
43
|
+
block.create_item :item_string, name: 'a-field'
|
44
|
+
```
|
45
|
+
|
46
|
+
## Config
|
23
47
|
|
24
48
|
Edit the conf file: `config/initializers/contents_core.rb`
|
25
49
|
|
26
50
|
```ruby
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
51
|
+
module ContentsCore
|
52
|
+
@@config = {
|
53
|
+
blocks: {
|
54
|
+
text: {
|
55
|
+
name: :text_only, # used as reference / for translations
|
56
|
+
children: { # children: sub blocks & items
|
57
|
+
title: :item_string,
|
58
|
+
content: :item_text
|
59
|
+
}
|
60
|
+
},
|
61
|
+
image: {
|
62
|
+
name: :image_only,
|
63
|
+
children: {
|
64
|
+
img: :item_file
|
65
|
+
}
|
66
|
+
},
|
67
|
+
slide: {
|
68
|
+
name: :a_slide,
|
69
|
+
child_only: true, # used only as child of another block (slider)
|
70
|
+
children: {
|
71
|
+
img: :item_file,
|
72
|
+
link: :item_string,
|
73
|
+
title: :item_string
|
74
|
+
}
|
75
|
+
},
|
76
|
+
slider: {
|
77
|
+
name: :a_slider,
|
78
|
+
new_children: :slide, # block type used when creating a new child with default params
|
79
|
+
children: {
|
80
|
+
slide: :slide
|
81
|
+
}
|
82
|
+
},
|
83
|
+
},
|
84
|
+
items: {
|
85
|
+
item_boolean: {},
|
86
|
+
item_datetime: {},
|
87
|
+
item_float: {},
|
88
|
+
item_hash: {},
|
89
|
+
item_file: {
|
90
|
+
input: :file_image
|
91
|
+
},
|
92
|
+
item_integer: {},
|
93
|
+
item_string: {},
|
94
|
+
item_text: {
|
95
|
+
input: :html
|
96
|
+
},
|
97
|
+
}
|
35
98
|
}
|
36
|
-
|
37
|
-
ContentsCore.config( { components: conf[:cc_blocks] } )
|
99
|
+
end
|
38
100
|
```
|
39
101
|
|
40
102
|
Create the new view blocks: `app/views/contents_core/_block_custom.html.slim`
|
@@ -46,7 +108,7 @@ Create the new view blocks: `app/views/contents_core/_block_custom.html.slim`
|
|
46
108
|
.image = image_tag block.get( 'image' ).url( :thumb )
|
47
109
|
```
|
48
110
|
|
49
|
-
|
111
|
+
### Images
|
50
112
|
|
51
113
|
To add support for images add CarrierWave gem to your Gemfile and execute: `rails generate uploader Image` and update che config file *config/initializers/contents_core.rb* with:
|
52
114
|
|
@@ -84,36 +146,36 @@ module ContentsCore
|
|
84
146
|
end
|
85
147
|
```
|
86
148
|
|
87
|
-
|
149
|
+
## Customizations
|
88
150
|
|
89
151
|
To create a "free form" block just use: `Page.first.create_block :intro, name: 'IntroBlock', schema: { intro: :item_string, subtitle: :item_string }`
|
90
152
|
|
91
153
|
Then create a *app/view/contents_core/_block_intro* view.
|
92
154
|
|
93
|
-
To list the blocks of a page: `Page.first.cc_blocks.pluck :name`
|
155
|
+
To list the blocks of a page manually (but *current_blocks* method is the preferred way): `Page.first.cc_blocks.pluck :name`
|
94
156
|
|
95
157
|
To add a new field to an existing block (ex. to first Page, on the first Block):
|
96
158
|
|
97
159
|
```rb
|
98
160
|
block = Page.first.get_block 'text-1'
|
99
|
-
block.create_item(
|
161
|
+
block.create_item( :item_string, name: 'new-field' ).set( 'A test...' ).save
|
100
162
|
```
|
101
163
|
|
102
164
|
Then add to the block view: `block.get( 'new-field' )`
|
103
165
|
|
104
166
|
To set a field value: `block.set( 'new-field', 'Some value' )`
|
105
167
|
|
106
|
-
|
168
|
+
### ActiveAdmin
|
107
169
|
|
108
170
|
If you use ActiveAdmin as admin interface you can find a sample model configuration: [page](extra/active_admin_page.rb) plus a js [page](extra/active_admin.js)
|
109
171
|
|
110
|
-
|
172
|
+
## Notes
|
111
173
|
|
112
|
-
- Blocks enum: `ContentsCore::Block.
|
113
|
-
- Blocks types: `ContentsCore::Block.
|
174
|
+
- Blocks enum: `ContentsCore::Block.enum`
|
175
|
+
- Blocks types: `ContentsCore::Block.types`
|
114
176
|
- Default blocks [here](config/initializers/contents_core.rb)
|
115
177
|
|
116
|
-
|
178
|
+
### Structure
|
117
179
|
|
118
180
|
- Including the Blocks concern to a model will add `has_many :cc_blocks` relationship (the list of blocks attached to a container) and some utility methods
|
119
181
|
- Block: UI component, a group of items (ex. a text with a title, a slider, a 3 column text widget, etc.); built with a list of sub blocks (for nested components) and a list of items
|
data/Rakefile
CHANGED
@@ -1,33 +1,35 @@
|
|
1
1
|
module ContentsCore
|
2
2
|
class Block < ApplicationRecord
|
3
|
-
|
3
|
+
# --- constants -----------------------------------------------------------
|
4
|
+
# EMPTY_DATA = OpenStruct.new( { data: '' } )
|
4
5
|
|
6
|
+
# --- misc ----------------------------------------------------------------
|
5
7
|
attr_accessor :create_children
|
8
|
+
serialize :conf, Hash
|
6
9
|
|
7
|
-
# ---
|
8
|
-
|
9
|
-
serialize :validations, JSON
|
10
|
-
|
11
|
-
# --- relations ---------------------------------------------------------- #
|
12
|
-
belongs_to :parent, polymorphic: true
|
10
|
+
# --- associations --------------------------------------------------------
|
11
|
+
belongs_to :parent, polymorphic: true, touch: true
|
13
12
|
has_many :cc_blocks, as: :parent, dependent: :destroy, foreign_key: 'parent_id', class_name: 'Block'
|
14
13
|
has_many :items, dependent: :destroy
|
15
14
|
accepts_nested_attributes_for :cc_blocks, allow_destroy: true
|
16
15
|
accepts_nested_attributes_for :items
|
17
16
|
|
18
|
-
# ---
|
17
|
+
# --- callbacks -----------------------------------------------------------
|
18
|
+
# after_initialize :on_after_initialize
|
19
19
|
before_create :on_before_create
|
20
20
|
after_create :on_after_create
|
21
21
|
|
22
|
-
# --- scopes
|
22
|
+
# --- scopes --------------------------------------------------------------
|
23
23
|
default_scope { order( :position ) }
|
24
24
|
scope :published, -> { where( published: true ) unless ContentsCore.editing }
|
25
25
|
scope :with_nested, -> { includes( :items, cc_blocks: :items ) }
|
26
26
|
|
27
|
-
# --- validations
|
27
|
+
# --- validations ---------------------------------------------------------
|
28
28
|
validates_presence_of :block_type, :position
|
29
|
+
validates_associated :cc_blocks
|
30
|
+
validates_associated :items
|
29
31
|
|
30
|
-
# ---
|
32
|
+
# --- tmp -----------------------------------------------------------------
|
31
33
|
## amoeba do
|
32
34
|
## enable
|
33
35
|
## # customize( lambda { |original_obj, new_obj|
|
@@ -53,11 +55,13 @@ module ContentsCore
|
|
53
55
|
#
|
54
56
|
# # scope :published, -> { where( published: true ) unless ApplicationController.edit_mode }
|
55
57
|
|
58
|
+
# --- methods -------------------------------------------------------------
|
56
59
|
def initialize( attributes = {}, &block )
|
57
60
|
super( attributes, &block )
|
58
|
-
@create_children =
|
61
|
+
@create_children = 0
|
62
|
+
self.conf = {} unless self.conf
|
59
63
|
self.group = config[:group]
|
60
|
-
self.block_type = parent.config[:
|
64
|
+
self.block_type = parent.config[:new_children] if attributes[:block_type].nil? && self.parent_type == 'ContentsCore::Block'
|
61
65
|
end
|
62
66
|
|
63
67
|
def as_json( options = nil )
|
@@ -68,19 +72,21 @@ module ContentsCore
|
|
68
72
|
"#{self.class.to_s.split('::').last}-#{self.id}"
|
69
73
|
end
|
70
74
|
|
71
|
-
def children_type
|
72
|
-
config[:children_type]
|
73
|
-
end
|
74
|
-
|
75
75
|
def config
|
76
|
-
ContentsCore.config[:
|
77
|
-
end
|
78
|
-
|
79
|
-
def create_item( item_type,
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
76
|
+
!self.conf.blank? ? self.conf : ( ContentsCore.config[:blocks][block_type.to_sym] ? ContentsCore.config[:blocks][block_type.to_sym] : {} )
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_item( item_type, options = {} )
|
80
|
+
if ContentsCore.config[:items].keys.include? item_type
|
81
|
+
attrs = { type: "ContentsCore::#{item_type.to_s.classify}" } # TODO: check if model exists
|
82
|
+
attrs[:name] = options[:name] if options[:name]
|
83
|
+
attrs[:data] = options[:value] if options[:value]
|
84
|
+
item = self.items.new attrs
|
85
|
+
item.save
|
86
|
+
item
|
87
|
+
else
|
88
|
+
raise "Invalid item type: #{item_type} - check defined items in config"
|
89
|
+
end
|
84
90
|
end
|
85
91
|
|
86
92
|
def editable
|
@@ -94,25 +100,27 @@ module ContentsCore
|
|
94
100
|
} :
|
95
101
|
{
|
96
102
|
'data-ec-block': self.id,
|
97
|
-
'data-ec-container': self.
|
103
|
+
'data-ec-container': self.new_children,
|
98
104
|
'data-ec-ct': self.block_type,
|
99
105
|
'data-ec-pub': self.published
|
100
106
|
}
|
101
107
|
).map { |k, v| "#{k}=\"#{v}\"" }.join( ' ' ).html_safe : ''
|
102
108
|
end
|
103
109
|
|
104
|
-
# Returns an item by name
|
110
|
+
# Returns an item value by name
|
105
111
|
def get( name )
|
106
112
|
item = get_item( name )
|
107
|
-
item.data
|
113
|
+
item && item.is_a?( Item ) ? item.data : nil
|
108
114
|
end
|
109
115
|
|
116
|
+
# Returns an item by name
|
110
117
|
def get_item( name )
|
111
|
-
|
112
|
-
|
113
|
-
|
118
|
+
t = tree
|
119
|
+
name.split( '.' ).each do |tok|
|
120
|
+
return nil unless t[tok]
|
121
|
+
t = t[tok]
|
114
122
|
end
|
115
|
-
|
123
|
+
t
|
116
124
|
end
|
117
125
|
|
118
126
|
def has_parent?
|
@@ -124,21 +132,30 @@ module ContentsCore
|
|
124
132
|
end
|
125
133
|
|
126
134
|
def is_sub_block?
|
127
|
-
parent.present? &&
|
135
|
+
self.parent.present? && self.parent.is_a?( Block )
|
136
|
+
end
|
137
|
+
|
138
|
+
def new_children
|
139
|
+
config[:new_children]
|
128
140
|
end
|
129
141
|
|
130
142
|
def on_after_create
|
131
143
|
# TODO: validates type before creation!
|
132
|
-
Block
|
144
|
+
Block.initialize_children( self, config[:children] ) if Block.types( false ).include?( self.block_type.to_sym )
|
133
145
|
end
|
134
146
|
|
147
|
+
# def on_after_initialize
|
148
|
+
# self.conf = {} unless self.conf
|
149
|
+
# end
|
150
|
+
|
135
151
|
def on_before_create
|
136
|
-
|
137
|
-
|
152
|
+
names = parent.cc_blocks.map( &:name )
|
153
|
+
if self.name.blank? || names.include?( self.name ) # Search a not used name
|
154
|
+
n = self.name.blank? ? block_type : self.name
|
138
155
|
i = 0
|
139
|
-
while( ( i += 1 ) < 1000 )
|
140
|
-
unless names.include? "#{
|
141
|
-
self.name = "#{
|
156
|
+
while( ( i += 1 ) < 1000 )
|
157
|
+
unless names.include? "#{n}-#{i}"
|
158
|
+
self.name = "#{n}-#{i}"
|
142
159
|
break
|
143
160
|
end
|
144
161
|
end
|
@@ -148,14 +165,15 @@ module ContentsCore
|
|
148
165
|
def props
|
149
166
|
pieces = {}
|
150
167
|
|
151
|
-
Item
|
152
|
-
pieces[type.pluralize.to_sym] = []
|
168
|
+
Item.types.each do |type|
|
169
|
+
pieces[type.to_s.pluralize.to_sym] = []
|
153
170
|
end
|
154
171
|
items.each do |item| # TODO: improve me
|
172
|
+
pieces[item.class.type_name.pluralize.to_sym] = [] unless pieces[item.class.type_name.pluralize.to_sym]
|
155
173
|
pieces[item.class.type_name.pluralize.to_sym].push item
|
156
174
|
end
|
157
|
-
Item
|
158
|
-
pieces[type
|
175
|
+
Item.types.each do |type|
|
176
|
+
pieces[type] = pieces[type.pluralize.to_sym].any? ? pieces[type.pluralize.to_sym].first : nil # EMPTY_DATA - empty Item per sti class?
|
159
177
|
end
|
160
178
|
|
161
179
|
# pieces = {
|
@@ -174,44 +192,62 @@ module ContentsCore
|
|
174
192
|
end
|
175
193
|
|
176
194
|
def set( name, value )
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
195
|
+
item = get_item( name )
|
196
|
+
item && item.is_a?( Item ) ? item.data = value : nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def tree
|
200
|
+
# return @items_tree if @items_tree
|
201
|
+
@items_tree = {} # prepare a complete list of items
|
202
|
+
self.items.each{ |item| @items_tree[item.name] = item }
|
203
|
+
self.cc_blocks.each_with_index{ |block, i| @items_tree[block.name] = block.tree } # @items_tree[i] = block.tree
|
204
|
+
@items_tree
|
183
205
|
end
|
184
206
|
|
185
|
-
def
|
186
|
-
|
207
|
+
def validations
|
208
|
+
config[:validations] || {}
|
187
209
|
end
|
188
210
|
|
189
|
-
def self.
|
190
|
-
ContentsCore.config[:
|
211
|
+
def self.enum( include_children = true )
|
212
|
+
ContentsCore.config[:blocks].map{|k, v| [I18n.t( 'contents_core.blocks.' + v[:name].to_s ), k.to_s] if !include_children || !v[:child_only]}.compact.sort_by{|b| b[0]}
|
191
213
|
end
|
192
214
|
|
193
|
-
def self.
|
194
|
-
|
215
|
+
def self.initialize_children( block, children, options = {} )
|
216
|
+
children.each do |name, type|
|
195
217
|
t = type.to_sym
|
196
|
-
if
|
218
|
+
if Item.types.include? t
|
197
219
|
c = 'ContentsCore::' + ActiveSupport::Inflector.camelize( t )
|
198
220
|
begin
|
199
221
|
model = c.constantize
|
200
222
|
rescue Exception => e
|
201
|
-
Rails.logger.error '[ERROR] ContentsCore -
|
223
|
+
Rails.logger.error '[ERROR] ContentsCore - initialize_children: ' + e.message
|
202
224
|
model = false
|
203
225
|
end
|
204
|
-
|
205
|
-
|
226
|
+
if model
|
227
|
+
block.items.new( type: model.name, name: name )
|
228
|
+
end
|
229
|
+
elsif Block.types( false ).include? t
|
206
230
|
block.create_children.times do
|
207
|
-
block.cc_blocks
|
231
|
+
block.cc_blocks.new( block_type: t, name: name )
|
232
|
+
block.save
|
208
233
|
end
|
209
234
|
end
|
210
|
-
end if
|
235
|
+
end if children
|
236
|
+
block.save
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.items_keys( keys )
|
240
|
+
keys.map do |k, v|
|
241
|
+
v.is_a?( Hash ) ? items_keys( v ) : k
|
242
|
+
end.flatten
|
211
243
|
end
|
212
244
|
|
213
245
|
def self.permitted_attributes
|
214
246
|
[ :id, :name, :block_type, :position, :_destroy, items_attributes: [ :id ] + Item::permitted_attributes, cc_blocks_attributes: [ :id, :name, :block_type, items_attributes: [ :id ] + Item::permitted_attributes ] ]
|
215
247
|
end
|
248
|
+
|
249
|
+
def self.types( include_children = true )
|
250
|
+
@@types ||= ContentsCore.config[:blocks].select{|k, v| !include_children || !v[:child_only]}.keys.map( &:to_sym )
|
251
|
+
end
|
216
252
|
end
|
217
253
|
end
|
@@ -1,10 +1,25 @@
|
|
1
1
|
module ContentsCore
|
2
2
|
class Item < ApplicationRecord
|
3
|
-
#
|
3
|
+
# --- associations --------------------------------------------------------
|
4
|
+
belongs_to :block, touch: true
|
5
|
+
|
6
|
+
# --- callbacks -----------------------------------------------------------
|
7
|
+
after_initialize :on_after_initialize
|
8
|
+
before_create :on_before_create
|
4
9
|
|
10
|
+
# --- misc ----------------------------------------------------------------
|
11
|
+
# field :data, type: String
|
5
12
|
# embedded_in :cc_blocks
|
6
13
|
|
7
|
-
|
14
|
+
# --- validations ---------------------------------------------------------
|
15
|
+
validate :validate_item
|
16
|
+
validates :block, presence: true, allow_blank: false
|
17
|
+
|
18
|
+
# --- methods -------------------------------------------------------------
|
19
|
+
def on_after_initialize
|
20
|
+
self.data = config[:default] if config[:default] && !self.data
|
21
|
+
self.init
|
22
|
+
end
|
8
23
|
|
9
24
|
def as_json( options = nil )
|
10
25
|
super( {only: [:id, :name, :type], methods: [:data]}.merge(options || {}) )
|
@@ -15,16 +30,44 @@ module ContentsCore
|
|
15
30
|
end
|
16
31
|
|
17
32
|
def class_name
|
18
|
-
self.class.to_s.split('::').last
|
33
|
+
self.class.to_s.split( '::' ).last
|
34
|
+
end
|
35
|
+
|
36
|
+
def config
|
37
|
+
@config ||= ( ContentsCore.config[:items] && ContentsCore.config[:items][self.class_name.underscore.to_sym] ? ContentsCore.config[:items][self.class_name.underscore.to_sym] : {} ).merge( self.block && self.block.config[:options] && self.name && self.block.config[:options][self.name.to_sym] ? self.block.config[:options][self.name.to_sym] : {} )
|
38
|
+
end
|
39
|
+
|
40
|
+
def data_type
|
41
|
+
@data_type ||= ( config[:data_type] || :string ).to_sym
|
19
42
|
end
|
20
43
|
|
21
44
|
def editable
|
22
45
|
ContentsCore.editing ? " data-ec-item=\"#{self.id}\" data-ec-input=\"#{self.opt_input}\" data-ec-type=\"#{self.class_name}\"".html_safe : ''
|
23
46
|
end
|
24
47
|
|
48
|
+
def init # placeholder method (for override)
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_before_create
|
52
|
+
# root_block = self.block
|
53
|
+
# root_block = root_block.parent while root_block.parent.is_a? Block
|
54
|
+
# names = Block.items_keys root_block.tree
|
55
|
+
names = ( self.block.items - [self] ).pluck :name
|
56
|
+
if self.name.blank? || names.include?( self.name ) # Search a not used name
|
57
|
+
n = self.name.blank? ? self.class.type_name : self.name
|
58
|
+
i = 0
|
59
|
+
while( ( i += 1 ) < 1000 )
|
60
|
+
unless names.include? "#{n}-#{i}"
|
61
|
+
self.name = "#{n}-#{i}"
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
25
68
|
def opt_input
|
26
|
-
if self.block.
|
27
|
-
self.block.
|
69
|
+
if self.block.config[self.name] && self.block.config[self.name]['input']
|
70
|
+
self.block.config[self.name]['input'].to_s
|
28
71
|
elsif config[:input]
|
29
72
|
config[:input].to_s
|
30
73
|
else
|
@@ -50,20 +93,20 @@ module ContentsCore
|
|
50
93
|
self.save
|
51
94
|
end
|
52
95
|
|
53
|
-
def
|
54
|
-
|
96
|
+
def validate_item
|
97
|
+
config[:validate].call( self ) if config[:validate]
|
55
98
|
end
|
56
99
|
|
57
100
|
def self.permitted_attributes
|
58
101
|
[ :data_boolean, :data_datetime, :data_file, :data_float, :data_hash, :data_integer, :data_string, :data_text ]
|
59
102
|
end
|
60
103
|
|
61
|
-
def
|
62
|
-
|
104
|
+
def self.type_name
|
105
|
+
''
|
63
106
|
end
|
64
107
|
|
65
|
-
def
|
66
|
-
|
108
|
+
def self.types
|
109
|
+
@@types ||= ContentsCore.config[:items].keys.map( &:to_sym )
|
67
110
|
end
|
68
111
|
|
69
112
|
protected
|
@@ -88,7 +131,7 @@ module ContentsCore
|
|
88
131
|
end
|
89
132
|
end
|
90
133
|
|
91
|
-
def converted_data
|
134
|
+
def converted_data
|
92
135
|
if data_type
|
93
136
|
case data_type
|
94
137
|
when :boolean
|
@@ -16,7 +16,7 @@ module ContentsCore
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def init
|
19
|
-
# self.data_file = File.open( ContentsCore::Engine.root.join( 'lib', 'data', '
|
19
|
+
# self.data_file = File.open( ContentsCore::Engine.root.join( 'lib', 'data', 'missing.jpg' ) ) unless self.data
|
20
20
|
self
|
21
21
|
end
|
22
22
|
|
@@ -31,7 +31,7 @@ module ContentsCore
|
|
31
31
|
# before_validation :on_before_validation
|
32
32
|
#
|
33
33
|
# def on_before_validation
|
34
|
-
# self.data = File.open( '
|
34
|
+
# self.data = File.open( 'test/public/img/img1.jpg' )
|
35
35
|
# # binding.pry
|
36
36
|
# end
|
37
37
|
|
@@ -1,12 +1,18 @@
|
|
1
1
|
# TODO: needs improvements
|
2
2
|
module ContentsCore
|
3
3
|
class ItemObject < Item
|
4
|
-
alias_attribute :data, :data_hash
|
5
|
-
|
6
4
|
serialize :data_hash, JSON
|
7
5
|
|
6
|
+
def data
|
7
|
+
self.data_hash.deep_symbolize_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def data=( value )
|
11
|
+
self.data_hash = value
|
12
|
+
end
|
13
|
+
|
8
14
|
def init
|
9
|
-
self.data = {}
|
15
|
+
self.data = {} unless self.data
|
10
16
|
self
|
11
17
|
end
|
12
18
|
|
@@ -2,20 +2,20 @@ module ContentsCore
|
|
2
2
|
class ItemString < Item
|
3
3
|
alias_attribute :data, :data_string
|
4
4
|
|
5
|
-
validate :on_validate
|
5
|
+
# validate :on_validate
|
6
6
|
|
7
7
|
def init
|
8
|
-
self.data = self.name.gsub( /-/, ' ' ).humanize
|
8
|
+
self.data = '' unless self.data # self.name.gsub( /-/, ' ' ).humanize
|
9
9
|
self
|
10
10
|
end
|
11
11
|
|
12
|
-
def on_validate
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
12
|
+
# def on_validate
|
13
|
+
# if self.block.validations[self.name]
|
14
|
+
# if self.block.validations[self.name] == 'required'
|
15
|
+
# self.errors.add( :base, "#{self.name} is required" ) if self.data_string.blank?
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# end
|
19
19
|
|
20
20
|
def update_data( value )
|
21
21
|
self.data = ActionController::Base.helpers.sanitize( value, tags: [] )
|
@@ -2,44 +2,49 @@ module ContentsCore
|
|
2
2
|
@@editing = false
|
3
3
|
|
4
4
|
@@config = {
|
5
|
-
|
5
|
+
blocks: {
|
6
6
|
image: {
|
7
7
|
name: 'Image block',
|
8
|
-
|
8
|
+
children: {
|
9
9
|
img: :item_file
|
10
10
|
}
|
11
11
|
},
|
12
12
|
multi_text: {
|
13
|
-
children_type: :text,
|
14
13
|
name: 'Multi columns block',
|
15
|
-
|
14
|
+
new_children: :text,
|
15
|
+
children: {
|
16
16
|
column: :text
|
17
|
-
}
|
17
|
+
},
|
18
18
|
},
|
19
19
|
slide: {
|
20
20
|
name: 'Slide block',
|
21
|
-
|
21
|
+
children: {
|
22
22
|
img: :item_file,
|
23
23
|
title: :item_string
|
24
24
|
}
|
25
25
|
},
|
26
26
|
slider: {
|
27
|
-
children_type: :slide,
|
28
27
|
name: 'Slider block',
|
29
|
-
|
28
|
+
new_children: :slide,
|
29
|
+
children: {
|
30
30
|
slide: :slide
|
31
31
|
}
|
32
32
|
},
|
33
33
|
text: {
|
34
34
|
name: 'Text block',
|
35
|
-
|
35
|
+
children: {
|
36
36
|
title: :item_string,
|
37
37
|
content: :item_text
|
38
|
-
}
|
38
|
+
},
|
39
|
+
# options: {
|
40
|
+
# title: {
|
41
|
+
# validate: ->( item ) { item.errors.add( :data_string, "can't be blank" ) if item.data.blank? }
|
42
|
+
# }
|
43
|
+
# }
|
39
44
|
},
|
40
45
|
text_with_image: {
|
41
46
|
name: 'Text with image block',
|
42
|
-
|
47
|
+
children: {
|
43
48
|
img: :item_file,
|
44
49
|
title: :item_string,
|
45
50
|
content: :item_text
|
@@ -47,18 +52,18 @@ module ContentsCore
|
|
47
52
|
},
|
48
53
|
},
|
49
54
|
items: {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
item_array: {},
|
56
|
+
item_boolean: {},
|
57
|
+
item_datetime: {},
|
58
|
+
item_float: {},
|
59
|
+
item_hash: {},
|
60
|
+
item_file: {
|
56
61
|
input: :file_image
|
57
62
|
},
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
item_integer: {},
|
64
|
+
item_object: {},
|
65
|
+
item_string: {},
|
66
|
+
item_text: {
|
62
67
|
input: :html
|
63
68
|
},
|
64
69
|
}
|
@@ -1,16 +1,16 @@
|
|
1
1
|
class CreateContentsCoreBlocks < ActiveRecord::Migration[5.0]
|
2
2
|
def change
|
3
3
|
create_table :contents_core_blocks do |t|
|
4
|
-
t.string
|
5
|
-
t.
|
6
|
-
t.string
|
7
|
-
t.
|
8
|
-
t.integer
|
9
|
-
t.boolean
|
10
|
-
t.
|
11
|
-
t.
|
12
|
-
t.
|
13
|
-
t.
|
4
|
+
t.string :block_type, null: false, default: 'text'
|
5
|
+
t.string :name
|
6
|
+
t.string :group
|
7
|
+
t.integer :version, null: false, default: 0
|
8
|
+
t.integer :position, null: false, default: 0
|
9
|
+
t.boolean :published, null: false, default: true
|
10
|
+
t.text :conf
|
11
|
+
t.integer :parent_id
|
12
|
+
t.string :parent_type
|
13
|
+
t.timestamps null: false
|
14
14
|
end
|
15
15
|
|
16
16
|
add_index :contents_core_blocks, [:parent_id, :parent_type]
|
@@ -1,9 +1,18 @@
|
|
1
1
|
class CreateContentsCoreItems < ActiveRecord::Migration[5.0]
|
2
2
|
def change
|
3
3
|
create_table :contents_core_items do |t|
|
4
|
-
t.string
|
5
|
-
t.string
|
6
|
-
t.integer
|
4
|
+
t.string :type
|
5
|
+
t.string :name
|
6
|
+
t.integer :block_id
|
7
|
+
t.boolean :data_boolean
|
8
|
+
t.datetime :data_datetime
|
9
|
+
t.string :data_file # , null: false, default: ''
|
10
|
+
t.float :data_float
|
11
|
+
t.text :data_hash
|
12
|
+
t.integer :data_integer
|
13
|
+
t.string :data_string # , null: false, default: ''
|
14
|
+
t.text :data_text
|
15
|
+
t.timestamps null: false
|
7
16
|
end
|
8
17
|
|
9
18
|
add_index :contents_core_items, :block_id
|
data/lib/contents_core.rb
CHANGED
@@ -10,11 +10,18 @@ module ContentsCore
|
|
10
10
|
def self.create_block_in_parent( parent, type = :text, params = {} )
|
11
11
|
block = Block.new( block_type: type )
|
12
12
|
block.name = params[:name] if params[:name]
|
13
|
-
block.
|
14
|
-
block.validations = params[:validations] if params[:validations]
|
13
|
+
block.conf = params[:conf] if params[:conf]
|
14
|
+
# block.validations = params[:validations] if params[:validations]
|
15
15
|
block.create_children = params[:create_children].to_i if params[:create_children]
|
16
|
-
parent.cc_blocks << block
|
17
|
-
Block::
|
16
|
+
parent.cc_blocks << block # TODO: change me (with cc_blocks.new)
|
17
|
+
Block::initialize_children block, params[:schema], {create_children: params[:create_children]} if params[:schema]
|
18
|
+
if params[:values]
|
19
|
+
traverse_hash block.tree, params[:values]
|
20
|
+
block.save
|
21
|
+
elsif params[:values_list]
|
22
|
+
params[:values_list].each{ |k, v| block.set k.to_s, v }
|
23
|
+
block.save
|
24
|
+
end
|
18
25
|
block
|
19
26
|
end
|
20
27
|
|
@@ -22,4 +29,25 @@ module ContentsCore
|
|
22
29
|
@@editing = editing unless editing.nil?
|
23
30
|
@@editing
|
24
31
|
end
|
32
|
+
|
33
|
+
def self.traverse_hash( hash, values )
|
34
|
+
ret = {}
|
35
|
+
values.each do |k, v|
|
36
|
+
if v.is_a? Hash
|
37
|
+
ret = traverse_hash hash[k.to_s], values[k]
|
38
|
+
else
|
39
|
+
hash[k.to_s].data = values[k]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
ret
|
43
|
+
end
|
44
|
+
|
45
|
+
# def self.parse_attr( attribute )
|
46
|
+
# attr = attribute.to_s
|
47
|
+
# if attr.include?( '.' )
|
48
|
+
# attrs = attr.split( '.' )
|
49
|
+
# attr = attrs.shift + attrs.map{|tok| "[#{tok}]"}.join
|
50
|
+
# end
|
51
|
+
# attr
|
52
|
+
# end
|
25
53
|
end
|
data/lib/contents_core/blocks.rb
CHANGED
@@ -12,9 +12,11 @@ module ContentsCore
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def current_blocks( version = 0 )
|
15
|
-
return @current_blocks if @current_blocks
|
15
|
+
# return @current_blocks if @current_blocks
|
16
16
|
version = 0 unless ContentsCore.editing # no admin = only current version
|
17
|
-
|
17
|
+
Rails.cache.fetch( "#{cache_key}/current_blocks/#{version}", expires_in: 12.hours ) do
|
18
|
+
self.cc_blocks.where( version: version.to_i ).with_nested.published
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def get_block( name, version = 0 )
|
@@ -23,6 +25,12 @@ module ContentsCore
|
|
23
25
|
end
|
24
26
|
nil
|
25
27
|
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def cache_key
|
32
|
+
self.cc_blocks.published.select( :updated_at ).order( updated_at: :desc ).first.try( :updated_at ).to_i
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contents_core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,44 +16,58 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5'
|
19
|
+
version: '5.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5'
|
26
|
+
version: '5.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: sqlite3
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '1.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '1.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
47
|
+
version: '0.11'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.11'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.15'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
description: A Rails gem which offer a
|
56
|
-
way
|
68
|
+
version: '0.15'
|
69
|
+
description: 'A Rails gem which offer a structure to manage contents in a flexible
|
70
|
+
way: blocks with recursive nested blocks + items as "leaves"'
|
57
71
|
email:
|
58
72
|
- mat@blocknot.es
|
59
73
|
executables: []
|
@@ -89,7 +103,6 @@ files:
|
|
89
103
|
- config/routes.rb
|
90
104
|
- db/migrate/20170414173603_create_contents_core_blocks.rb
|
91
105
|
- db/migrate/20170414173610_create_contents_core_items.rb
|
92
|
-
- db/migrate/20170414173611_add_extra_items.rb
|
93
106
|
- lib/contents_core.rb
|
94
107
|
- lib/contents_core/blocks.rb
|
95
108
|
- lib/contents_core/engine.rb
|
@@ -1,12 +0,0 @@
|
|
1
|
-
class AddExtraItems < ActiveRecord::Migration[5.0]
|
2
|
-
def change
|
3
|
-
add_column :contents_core_items, :data_boolean, :boolean
|
4
|
-
add_column :contents_core_items, :data_datetime, :datetime
|
5
|
-
add_column :contents_core_items, :data_file, :string # , null: false, default: ''
|
6
|
-
add_column :contents_core_items, :data_float, :float
|
7
|
-
add_column :contents_core_items, :data_hash, :text
|
8
|
-
add_column :contents_core_items, :data_integer, :integer
|
9
|
-
add_column :contents_core_items, :data_string, :string # , null: false, default: ''
|
10
|
-
add_column :contents_core_items, :data_text, :text
|
11
|
-
end
|
12
|
-
end
|