contents_core 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/contents_core.svg)](https://badge.fury.io/rb/contents_core) [![Build Status](https://travis-ci.org/blocknotes/contents_core.svg)](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
|