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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75a1c3e706d549e2a1760b2986ab6ed1c947e62c
4
- data.tar.gz: 50c1f08869d686fb75509b3d26a92c8b59873196
3
+ metadata.gz: 33b635a47b2e5a090eea2d2c99dba30eef64c508
4
+ data.tar.gz: 93e8aa348c86bc228f0b0572a7fbdbec58fe5ad4
5
5
  SHA512:
6
- metadata.gz: 9ac52395df24233ceda982dceab0871716058adf72bfd99cb5fbb98b67ce519b7830186c37b68441a7542b7a11658a0127c9ea5f7084a4bf0875228a4fafe9e2
7
- data.tar.gz: 92ccd52237adcb6bf89352b9d6d583984daac7c54aadb3659268efdca2dfe298eff598c24a0e6ca0545afa9f0286e75dbae82e6817fdf1a25fe63caa63bdd9fb
6
+ metadata.gz: cb9f8914cbd38b7d91ae103c317c8cbc6310153f2a35d9b294d98c5abfcc84ee919cbbd406e89590ab935bf23cc82dab254e4ed315734c63350957cd0887594e
7
+ data.tar.gz: 3b341cdfe78f537cf7118fedc018d49bf834d4677fd1358cecdc2522bef8747411e951e92adee917df5556cadc04c8fb294ef0d31e35d5104513484e2fe74db5
@@ -1,4 +1,4 @@
1
- Copyright 2017
1
+ Copyright 2017, Mattia Roccoberton
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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 simple structure to manage contents in a flexible way.
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
- ### Install
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
- - Add the blocks to a view (ex. *page show*): `= render partial: 'contents_core/blocks', locals: { container: @page }`
21
+ - Optionally add the blocks to a view (ex. *page show*): `= render partial: 'contents_core/blocks', locals: { container: @page }`
21
22
 
22
- ### Config
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
- conf = ContentsCore.config
28
- # Adds a new custom block
29
- conf[:cc_blocks][:custom] = {
30
- name: 'Custom block',
31
- items: {
32
- title: :item_string,
33
- content: :item_text,
34
- image: :item_file
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
- #### Images
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
- #### Customizations
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( 'ContentsCore::ItemString', 'new-field' ).set( 'A test...' ).save
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
- #### ActiveAdmin
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
- ### Notes
172
+ ## Notes
111
173
 
112
- - Blocks enum: `ContentsCore::Block.block_enum`
113
- - Blocks types: `ContentsCore::Block.block_types`
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
- #### Structure
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
@@ -21,7 +21,6 @@ load 'rails/tasks/engine.rake'
21
21
  load 'rails/tasks/statistics.rake'
22
22
 
23
23
 
24
-
25
24
  require 'bundler/gem_tasks'
26
25
 
27
26
  require 'rake/testtask'
@@ -1,33 +1,35 @@
1
1
  module ContentsCore
2
2
  class Block < ApplicationRecord
3
- EMPTY_DATA = OpenStruct.new( { data: '' } )
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
- # --- fields options ----------------------------------------------------- #
8
- serialize :options, JSON
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
- # --- hooks -------------------------------------------------------------- #
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
- # --- misc --------------------------------------------------------------- #
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 = 1
61
+ @create_children = 0
62
+ self.conf = {} unless self.conf
59
63
  self.group = config[:group]
60
- self.block_type = parent.config[:children_type] if attributes[:block_type].nil? && self.parent_type == 'ContentsCore::Block'
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[:cc_blocks][block_type.to_sym] ? ContentsCore.config[:cc_blocks][block_type.to_sym] : {}
77
- end
78
-
79
- def create_item( item_type, item_name = nil )
80
- new_item = ContentsCore::Item.new( type: item_type )
81
- new_item.name = item_name if item_name
82
- self.items << new_item
83
- new_item
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.children_type,
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 if item
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
- unless @_items
112
- @_items = {}
113
- items.each { |item| @_items[item.name] = item }
118
+ t = tree
119
+ name.split( '.' ).each do |tok|
120
+ return nil unless t[tok]
121
+ t = t[tok]
114
122
  end
115
- @_items[name]
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? && parent_type == 'ContentsCore::Block'
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::init_items( self, config[:items] ) if Block::block_types( false ).include?( self.block_type.to_sym )
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
- if self.name.blank?
137
- names = parent.cc_blocks.map( &:name )
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 ) # Search an empty group
140
- unless names.include? "#{block_type}-#{i}"
141
- self.name = "#{block_type}-#{i}"
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::item_types.each do |type|
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::item_types.each do |type|
158
- pieces[type.to_sym] = pieces[type.pluralize.to_sym].any? ? pieces[type.pluralize.to_sym].first : nil # EMPTY_DATA - empty Item per sti class?
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
- items.each do |item|
178
- if item.name == name
179
- item.data = value
180
- break
181
- end
182
- end
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 self.block_enum( include_children = true )
186
- ContentsCore.config[:cc_blocks].map{|k, v| [v[:name], k.to_s] if !include_children || !v[:child_only]}.compact.sort_by{|b| b[0]}
207
+ def validations
208
+ config[:validations] || {}
187
209
  end
188
210
 
189
- def self.block_types( include_children = true )
190
- ContentsCore.config[:cc_blocks].select{|k, v| !include_children || !v[:child_only]}.keys
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.init_items( block, items, options = {} )
194
- items.each do |name, type|
215
+ def self.initialize_children( block, children, options = {} )
216
+ children.each do |name, type|
195
217
  t = type.to_sym
196
- if type.to_s.start_with? 'item_'
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 - init_items: ' + e.message
223
+ Rails.logger.error '[ERROR] ContentsCore - initialize_children: ' + e.message
202
224
  model = false
203
225
  end
204
- block.items << model.new( name: name ).init if model
205
- elsif Block::block_types( false ).include? t.to_sym
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 << Block.new( block_type: t, name: name )
231
+ block.cc_blocks.new( block_type: t, name: name )
232
+ block.save
208
233
  end
209
234
  end
210
- end if items
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
- # field :data, type: String
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
- belongs_to :block
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.options[self.name] && self.block.options[self.name]['input']
27
- self.block.options[self.name]['input'].to_s
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 self.item_types
54
- @@item_types ||= ContentsCore.config[:items].keys.map( &:to_s )
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 config
62
- @config ||= self.block && self.block.config[:options] && self.block.config[:options][self.name.to_sym] ? self.block.config[:options][self.name.to_sym] : ( ContentsCore.config[:items][self.class::type_name.to_sym] ? ContentsCore.config[:items][self.class::type_name.to_sym] : {} )
104
+ def self.type_name
105
+ ''
63
106
  end
64
107
 
65
- def data_type
66
- @data_type ||= ( config[:data_type] || :string ).to_sym
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
@@ -43,7 +43,11 @@ module ContentsCore
43
43
  end
44
44
 
45
45
  def init
46
- self.data_string = []
46
+ unless self.data
47
+ self.data_string = []
48
+ self.data_hash = []
49
+ self.data_text = []
50
+ end
47
51
  self
48
52
  end
49
53
 
@@ -3,7 +3,7 @@ module ContentsCore
3
3
  alias_attribute :data, :data_boolean
4
4
 
5
5
  def init
6
- self.data = 0
6
+ self.data = false unless self.data
7
7
  self
8
8
  end
9
9
 
@@ -3,7 +3,7 @@ module ContentsCore
3
3
  alias_attribute :data, :data_datetime
4
4
 
5
5
  def init
6
- self.data = Time.now
6
+ self.data = Time.now unless self.data
7
7
  self
8
8
  end
9
9
 
@@ -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', 'img1.jpg' ) )
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( 'rails/editable_components_test/public/img/img1.jpg' )
34
+ # self.data = File.open( 'test/public/img/img1.jpg' )
35
35
  # # binding.pry
36
36
  # end
37
37
 
@@ -3,7 +3,7 @@ module ContentsCore
3
3
  alias_attribute :data, :data_float
4
4
 
5
5
  def init
6
- self.data = 0
6
+ self.data = 0 unless self.data
7
7
  self
8
8
  end
9
9
 
@@ -5,7 +5,7 @@ module ContentsCore
5
5
  serialize :data_hash, Hash
6
6
 
7
7
  def init
8
- self.data = {}
8
+ self.data = {} unless self.data
9
9
  self
10
10
  end
11
11
 
@@ -3,7 +3,7 @@ module ContentsCore
3
3
  alias_attribute :data, :data_integer
4
4
 
5
5
  def init
6
- self.data = 0
6
+ self.data = 0 unless self.data
7
7
  self
8
8
  end
9
9
 
@@ -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
- 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
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: [] )
@@ -3,7 +3,7 @@ module ContentsCore
3
3
  alias_attribute :data, :data_text
4
4
 
5
5
  def init
6
- self.data = self.name.gsub( /-/, ' ' ).humanize
6
+ self.data = '' unless self.data # self.name.gsub( /-/, ' ' ).humanize
7
7
  self
8
8
  end
9
9
 
@@ -2,44 +2,49 @@ module ContentsCore
2
2
  @@editing = false
3
3
 
4
4
  @@config = {
5
- cc_blocks: {
5
+ blocks: {
6
6
  image: {
7
7
  name: 'Image block',
8
- items: {
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
- items: {
14
+ new_children: :text,
15
+ children: {
16
16
  column: :text
17
- }
17
+ },
18
18
  },
19
19
  slide: {
20
20
  name: 'Slide block',
21
- items: {
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
- items: {
28
+ new_children: :slide,
29
+ children: {
30
30
  slide: :slide
31
31
  }
32
32
  },
33
33
  text: {
34
34
  name: 'Text block',
35
- items: {
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
- items: {
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
- array: {},
51
- boolean: {},
52
- datetime: {},
53
- float: {},
54
- hash: {},
55
- file: {
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
- integer: {},
59
- object: {},
60
- string: {},
61
- text: {
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 :block_type, null: false, default: 'text'
5
- t.integer :version, null: false, default: 0
6
- t.string :name, null: false, default: ''
7
- t.string :group
8
- t.integer :position, null: false, default: 0
9
- t.boolean :published, null: false, default: true
10
- t.string :options, null: false, default: '{}'
11
- t.string :validations, null: false, default: '{}'
12
- t.integer :parent_id
13
- t.string :parent_type
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 :type
5
- t.string :name, null: false, default: 'data'
6
- t.integer :block_id
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
@@ -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.options = params[:options] if params[:options]
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::init_items block, params[:schema], {create_children: params[:create_children]} if params[:schema]
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
@@ -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
- @current_blocks = cc_blocks.where( version: version.to_i ).with_nested.published
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
@@ -1,3 +1,3 @@
1
1
  module ContentsCore
2
- VERSION = '0.2.5'
2
+ VERSION = '0.3.0'
3
3
  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.2.5
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-02 00:00:00.000000000 Z
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: '0'
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: '0'
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 simple structure to manage contents in a flexible
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