nocms-blocks 1.0.0 → 1.1.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/CHANGELOG +10 -0
- data/README.md +7 -3
- data/app/models/no_cms/blocks/block.rb +31 -128
- data/app/models/no_cms/blocks/concerns/serializing_fields.rb +386 -0
- data/app/models/no_cms/blocks/layout.rb +98 -0
- data/app/views/no_cms/blocks/blocks/_default.html.erb +6 -0
- data/db/migrate/20150709132202_add_non_translated_fields_info_to_no_cms_blocks_block.rb +5 -0
- data/db/migrate/20150710112549_move_layout_from_no_cms_blocks_block_translations_to_no_cms_blocks_blocks.rb +19 -0
- data/lib/generators/nocms/templates/config/initializers/nocms/blocks.rb +8 -0
- data/lib/no_cms/blocks/version.rb +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +3 -2
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +163 -0
- data/spec/dummy/log/test.log +94024 -0
- data/spec/dummy/public/uploads/test_image/logo/2/logo.png +0 -0
- data/spec/dummy/public/uploads/test_image/logo/2/logo2.png +0 -0
- data/spec/dummy/public/uploads/test_image/logo/3/logo.png +0 -0
- data/spec/dummy/public/uploads/test_image/logo/4/logo.png +0 -0
- data/spec/dummy/public/uploads/test_image/logo/5/logo.png +0 -0
- data/spec/dummy/public/uploads/test_image/logo/6/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-1395/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-1590/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-1891/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-1891/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-2425/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-3650/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-3867/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-3867/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-5629/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-5795/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-5882/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194769-17662-9375/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-0450/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-0733/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-0733/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-1087/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-1087/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-1788/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-2101/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-4443/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-5065/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-6456/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-8131/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194790-17727-9096/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194856-17785-0815/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194856-17785-4789/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194856-17785-7685/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-0259/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-0349/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-0476/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-1997/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-3813/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-3813/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-4455/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-5316/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-5316/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-7121/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-7447/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-7677/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-8037/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194857-17785-8791/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194878-17844-1352/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194878-17844-1557/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194878-17844-5177/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194878-17844-5338/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194878-17844-8747/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-3191/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-3986/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-5951/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-6104/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-7114/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-7114/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-7617/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-8185/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-8495/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-8495/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-8991/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194879-17844-9466/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194892-17862-1771/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436194926-17904-9747/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195033-17981-2265/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195045-18015-3603/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195085-18055-8412/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195101-18097-0044/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195126-18165-6939/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195152-18225-1850/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195169-18262-2283/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195186-18300-3395/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195205-18336-0453/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195242-18389-3554/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195255-18420-9336/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195745-18605-6163/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195823-18669-7443/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195886-18739-5334/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195900-18776-9749/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436195935-18824-1992/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196060-18894-4158/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196106-18943-1035/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196156-18987-1445/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196187-19045-9003/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196275-19125-3257/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436196321-19169-5591/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-0160/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-0565/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-1519/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-2202/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-2400/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-2928/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-3260/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-4333/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-5122/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-6333/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-6333/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-7928/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-9359/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-9392/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-9549/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-9549/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258374-8961-9648/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258382-8967-0797/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258453-9165-7375/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258549-9282-2039/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258623-9355-6670/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436258674-9394-2929/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436264869-10860-2169/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276653-16968-2375/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276734-17282-9777/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276770-17431-4333/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276781-17463-5971/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276875-17556-1972/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276882-17588-9186/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436276896-17657-9942/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436277157-17795-9344/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436277178-17833-8883/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436277781-18431-2830/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436278571-18860-1079/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436282613-22892-0135/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436282638-22953-7833/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436282887-23026-0068/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436282978-23160-8886/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-1740/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-3170/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-3501/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-6393/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-6862/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-7741/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-8051/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-8064/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-8093/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436284256-24634-9497/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-0200/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-0325/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-1380/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-4150/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-4347/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-5072/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-5891/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-6304/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-6620/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-6703/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-8254/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436553862-9485-8313/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-0021/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-1865/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-2510/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-3771/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-4441/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-5712/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-6456/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-6520/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-8183/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-8675/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-8953/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554155-9646-9271/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554181-9685-7099/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554233-9708-0769/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554254-9739-7026/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554350-9834-3927/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554416-9875-0666/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554519-9921-2596/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554569-9980-0004/logo.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554694-10292-1775/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554694-10292-5549/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554722-10308-2875/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554732-10340-8854/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436554994-10463-9859/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436555134-10554-3506/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436555227-10604-7612/logo2.png +0 -0
- data/spec/dummy/public/uploads/tmp/1436555450-10700-1633/logo2.png +0 -0
- data/spec/models/no_cms/blocks/duplicating_blocks_spec.rb +161 -0
- data/spec/models/no_cms/blocks/i18n_blocks_spec.rb +101 -0
- data/spec/models/no_cms/blocks/layout_spec.rb +125 -0
- data/spec/spec_helper.rb +3 -0
- metadata +385 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbc9eae202e6ec4df5bdba9d91c4a842e03e2f3d
|
|
4
|
+
data.tar.gz: 2523df1872e26d06a714b3bc9c8552c49c07c279
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 841a55342e8a5b30e2a4c4a5c2771d9d231a4ce68fbe483896159823d44332130fc2b09b8ba214927cb395858301565bac8ef03a98e34c9df8e1ca67b7911ec3
|
|
7
|
+
data.tar.gz: cefb0459db81a0af3dd4e10f3da524b6c6d5359a0aecfe5b1b6cd5664d30c1d09315bc0a5615dab8ad2cea01f16cc5ffd5a3f0b2bc9a0c36c37b883529e0c1fe
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
1.1.0
|
|
2
|
+
|
|
3
|
+
- I18n behaviour fixed
|
|
4
|
+
* Separated objects cache for translations
|
|
5
|
+
* fields info both in the block and the translation
|
|
6
|
+
|
|
7
|
+
- Globalize dependency correctly specified in the gemspec
|
|
8
|
+
|
|
9
|
+
- Latest versions of awesome-nested-set (3.0.2) and globalize (5.0.1)
|
|
10
|
+
|
|
1
11
|
1.0.0
|
|
2
12
|
|
|
3
13
|
- First released version.
|
data/README.md
CHANGED
|
@@ -6,15 +6,19 @@ This is a Rails engine with a basic functionality of customizable blocks of cont
|
|
|
6
6
|
|
|
7
7
|
## How do I install it?
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Just include in your Gemfile:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```ruby
|
|
12
|
+
gem "nocms-blocks"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If you're brave and want to use the last development version you can use:
|
|
12
16
|
|
|
13
17
|
```ruby
|
|
14
18
|
gem "nocms-blocks", git: 'git@github.com:simplelogica/nocms-blocks.git'
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
Once the gem is installed you can import all the migrations:
|
|
18
22
|
|
|
19
23
|
```
|
|
20
24
|
rake no_cms_blocks:install:migrations
|
|
@@ -9,152 +9,55 @@ module NoCms::Blocks
|
|
|
9
9
|
scope :no_drafts, ->() { where_with_locale(draft: false) }
|
|
10
10
|
scope :roots, ->() { where parent_id: nil }
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
attr_reader :cached_objects
|
|
12
|
+
translates :draft
|
|
13
|
+
include NoCms::Blocks::Concerns::SerializingFields
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
serialize :fields_info, Hash
|
|
15
|
+
##
|
|
16
|
+
# In the block we get all the fields so it can accept all of them
|
|
17
|
+
def fields_configuration
|
|
18
|
+
layout_config.fields
|
|
21
19
|
end
|
|
22
20
|
|
|
23
|
-
after_initialize :set_blank_fields
|
|
24
|
-
before_save :save_related_objects
|
|
25
|
-
|
|
26
|
-
validates :fields_info, presence: { allow_blank: true }
|
|
27
21
|
validates :layout, presence: true
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def layout_config
|
|
35
|
-
NoCms::Blocks.block_layouts.stringify_keys[layout]
|
|
36
|
-
end
|
|
23
|
+
##
|
|
24
|
+
# A block dups all it's children and the translations
|
|
25
|
+
def duplicate_self new_self
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def has_field? field
|
|
43
|
-
# We have the field if...
|
|
44
|
-
!layout_config.nil? && # We have a layout configuration AND
|
|
45
|
-
(
|
|
46
|
-
!layout_config[:fields].symbolize_keys[field.to_sym].nil? || # We have this field OR
|
|
47
|
-
!layout_config[:fields].symbolize_keys[field.to_s.gsub(/\_id$/, '').to_sym].nil? # we remove the final _id and then we have the field
|
|
48
|
-
)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def field_type field
|
|
52
|
-
return nil unless has_field?(field)
|
|
53
|
-
layout_config[:fields].symbolize_keys[field.to_sym]
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def read_field field
|
|
57
|
-
return nil unless has_field?(field)
|
|
58
|
-
|
|
59
|
-
value = fields_info[field.to_sym] || # first, we get the value
|
|
60
|
-
@cached_objects[field.to_sym] # or we get it from the cached objects
|
|
61
|
-
|
|
62
|
-
# If value is still nil, but the field exists we must get the object from the database
|
|
63
|
-
if value.nil?
|
|
64
|
-
field_type = field_type(field)
|
|
65
|
-
field_id = fields_info["#{field}_id".to_sym]
|
|
66
|
-
value = @cached_objects[field.to_sym] = field_type.find(field_id) unless field_id.nil?
|
|
67
|
-
end
|
|
27
|
+
new_self.translations = translations.map(&:dup)
|
|
28
|
+
new_self.translations.each { |t| t.globalized_model = new_self }
|
|
68
29
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
value = @cached_objects[field.to_sym] = field_type.new
|
|
30
|
+
children.each do |child|
|
|
31
|
+
new_self.children << child.dup
|
|
72
32
|
end
|
|
73
|
-
value
|
|
74
33
|
end
|
|
75
34
|
|
|
76
|
-
|
|
77
|
-
return nil unless has_field?(field)
|
|
78
|
-
field_type = field_type field
|
|
79
|
-
# If field type is a model then we update the cached object
|
|
80
|
-
if field_type.is_a?(Class) && field_type < ActiveRecord::Base
|
|
81
|
-
# First, we initialize the object if we don't read the object (it loads it into the cached objects)
|
|
82
|
-
@cached_objects[field.to_sym] = field_type.new if read_field(field).nil?
|
|
83
|
-
# Then, assign attributes
|
|
84
|
-
@cached_objects[field.to_sym].assign_attributes value
|
|
85
|
-
else # If it's a model then a new object or update the previous one
|
|
86
|
-
self.fields_info = fields_info.merge field.to_sym => value # when updating through an object (i.e. the page updates through nested attributes) fields_info[field.to_sym] = value doesn't work. Kudos to Rubo for this fix
|
|
87
|
-
end
|
|
88
|
-
end
|
|
35
|
+
class Translation
|
|
89
36
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
field = m.to_s
|
|
95
|
-
write_accessor = field.ends_with? '='
|
|
96
|
-
field.gsub!(/\=$/, '')
|
|
97
|
-
|
|
98
|
-
# If this field actually exists, then we write it or read it.
|
|
99
|
-
if has_field?(field)
|
|
100
|
-
write_accessor ?
|
|
101
|
-
write_field(field, args.first) :
|
|
102
|
-
read_field(field.to_sym)
|
|
103
|
-
else
|
|
104
|
-
super
|
|
37
|
+
##
|
|
38
|
+
# In the translation we get only the translated fields
|
|
39
|
+
def fields_configuration
|
|
40
|
+
layout_config.translated_fields
|
|
105
41
|
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# When we are assigning attributes (this method is called in new, create...)
|
|
109
|
-
# we must split those fields from our current layout and those who are not
|
|
110
|
-
# (they must be attributes).
|
|
111
|
-
# Attributes are processed the usual way and fields are written later
|
|
112
|
-
def assign_attributes new_attributes
|
|
113
|
-
fields = []
|
|
114
|
-
|
|
115
|
-
set_blank_fields
|
|
116
|
-
|
|
117
|
-
# We get the layout
|
|
118
|
-
new_layout = new_attributes[:layout] || new_attributes['layout']
|
|
119
|
-
self.layout = new_layout unless new_layout.nil?
|
|
120
|
-
|
|
121
|
-
Rails.logger.info "Searching #{new_attributes.keys.inspect} fields in #{self.layout} layout"
|
|
122
|
-
|
|
123
|
-
# And now separate fields and attributes
|
|
124
|
-
fields = new_attributes.select{|k, _| has_field? k }.symbolize_keys
|
|
125
|
-
new_attributes.reject!{|k, _| has_field? k }
|
|
126
42
|
|
|
127
|
-
|
|
43
|
+
##
|
|
44
|
+
# Sometimes the translation still won't have the globalized_model linked
|
|
45
|
+
# (e.g. before it's saved for the first time) and we must have a mechanism
|
|
46
|
+
# to be able to store a temporary layout
|
|
47
|
+
attr_accessor :layout
|
|
128
48
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
49
|
+
##
|
|
50
|
+
# If we don't have a globalized model yet we return our temporary layout
|
|
51
|
+
def layout
|
|
52
|
+
globalized_model.nil? ? @layout : globalized_model.layout
|
|
133
53
|
end
|
|
134
|
-
end
|
|
135
54
|
|
|
136
|
-
|
|
137
|
-
@cached_objects = {}
|
|
138
|
-
super
|
|
55
|
+
include NoCms::Blocks::Concerns::SerializingFields
|
|
139
56
|
end
|
|
140
57
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def set_blank_fields
|
|
144
|
-
self.fields_info ||= {}
|
|
145
|
-
@cached_objects ||= {}
|
|
146
|
-
end
|
|
58
|
+
accepts_nested_attributes_for :children, allow_destroy: true
|
|
59
|
+
accepts_nested_attributes_for :translations
|
|
147
60
|
|
|
148
|
-
def save_related_objects
|
|
149
|
-
# Now we save each activerecord related object
|
|
150
|
-
cached_objects.each do |field, object|
|
|
151
|
-
# Notice that we don't care if the object is actually saved
|
|
152
|
-
# We don't care because there may be some cases where no real information is sent to an object but something is sent (i.e. the locale in a new Globalize translation) and then the object is created empty
|
|
153
|
-
# When this happens if we save! the object an error is thrown and we can't leave the object blank
|
|
154
|
-
if object.is_a?(ActiveRecord::Base) && object.save
|
|
155
|
-
fields_info["#{field}_id".to_sym] = object.id
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
61
|
end
|
|
62
|
+
|
|
160
63
|
end
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
module NoCms
|
|
2
|
+
module Blocks
|
|
3
|
+
module Concerns
|
|
4
|
+
module SerializingFields
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
self.included do
|
|
8
|
+
|
|
9
|
+
serialize :fields_info, Hash
|
|
10
|
+
|
|
11
|
+
after_initialize :set_blank_fields
|
|
12
|
+
|
|
13
|
+
before_save :save_related_objects
|
|
14
|
+
|
|
15
|
+
validates :fields_info, presence: { allow_blank: true }
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# This attribute stores all the objects referenced on those fields
|
|
19
|
+
# from an AR subtype.
|
|
20
|
+
#
|
|
21
|
+
# It acts both as a cache and as a way to edit or create AR objects
|
|
22
|
+
# and save them when the block is saved.
|
|
23
|
+
attr_reader :cached_objects
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# This method checks wether the block's layout has cache configured
|
|
28
|
+
# and returns it.
|
|
29
|
+
#
|
|
30
|
+
# If it hasn't it returns the global cache_enabled configuration for
|
|
31
|
+
# NoCms::Blocks
|
|
32
|
+
def cache_enabled?
|
|
33
|
+
layout_config.cache_enabled?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Old version without '?' mantained for historical reasons
|
|
38
|
+
alias_method :cache_enabled, :cache_enabled?
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Returns a hash with the layout information read from the blocks
|
|
42
|
+
# initializer and the 'layout' field on the corresponding object.
|
|
43
|
+
def layout_config
|
|
44
|
+
return if layout.nil?
|
|
45
|
+
@layout_config ||= NoCms::Blocks::Layout.find layout
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Returns the template read from the layout configuration
|
|
50
|
+
def template
|
|
51
|
+
layout_config.template if layout_config
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# This method checks that we have the field passed as parameter in our
|
|
56
|
+
# layout configuration.
|
|
57
|
+
#
|
|
58
|
+
# It also takes into account the case where we have an AR object and
|
|
59
|
+
# we're asking just for its id.
|
|
60
|
+
def has_field? field
|
|
61
|
+
# We have the field if...
|
|
62
|
+
!layout_config.nil? && # We have a layout configuration AND
|
|
63
|
+
!layout_config.field(field).nil? # We have this field
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# This method tells wether we are in a translation and we must manage
|
|
68
|
+
# translated fields or not
|
|
69
|
+
def is_translation?
|
|
70
|
+
!self.respond_to?(:translations)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Returns the type of a field in the current layout configuration of
|
|
75
|
+
# this block.
|
|
76
|
+
#
|
|
77
|
+
# If the field is not present in the layout configuration it returns
|
|
78
|
+
# nil.
|
|
79
|
+
def field_type field
|
|
80
|
+
return nil unless has_field?(field)
|
|
81
|
+
fields_configuration.symbolize_keys[field.to_sym][:type]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# Returns the stored value of the field for this block.
|
|
86
|
+
#
|
|
87
|
+
# If the field is not present in the layout configuration it returns
|
|
88
|
+
# nil.
|
|
89
|
+
#
|
|
90
|
+
# If the field is an Active Record object but it's not present on our
|
|
91
|
+
# objects cache we fetch it from the database using the id stored in
|
|
92
|
+
# the #{field}_id field.
|
|
93
|
+
#
|
|
94
|
+
# If it's an Active Record object but we don't have the #{field}_id
|
|
95
|
+
# field then it creates (with new, not with create) a new one and
|
|
96
|
+
# stores it in the objects cache. Later, if the block is saved, this
|
|
97
|
+
# object will be saved too.
|
|
98
|
+
def read_field field
|
|
99
|
+
raise NoMethodError.new("field #{field} is not defined in the block layout") unless has_field?(field)
|
|
100
|
+
|
|
101
|
+
# first, we get the value
|
|
102
|
+
value = fields_info[field.to_sym] ||
|
|
103
|
+
# or we get it from the cached objects
|
|
104
|
+
@cached_objects[field.to_sym]
|
|
105
|
+
|
|
106
|
+
# If value is still nil, but the field exists we must get the object
|
|
107
|
+
# from the database
|
|
108
|
+
if value.nil?
|
|
109
|
+
field_type = field_type(field)
|
|
110
|
+
field_id = fields_info["#{field}_id".to_sym]
|
|
111
|
+
unless field_id.nil?
|
|
112
|
+
value = field_type.find(field_id)
|
|
113
|
+
@cached_objects[field.to_sym] = value
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# If value is still nil, and the field_type is an ActiveRecord
|
|
118
|
+
# class, then we build a new one
|
|
119
|
+
if value.nil? && field_type.is_a?(Class)
|
|
120
|
+
value = field_type.new
|
|
121
|
+
@cached_objects[field.to_sym] = value
|
|
122
|
+
end
|
|
123
|
+
value
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# This method stores the parameter value into the corresponding field.
|
|
128
|
+
#
|
|
129
|
+
# If the field is an Active Record object then we load it into the
|
|
130
|
+
# objects cache and assign it the value through an assign_attributes.
|
|
131
|
+
# This solves the scenario of a nested form where a hash is passed as
|
|
132
|
+
# the value of the field.
|
|
133
|
+
def write_field field, value
|
|
134
|
+
raise NoMethodError.new("field #{field} is not defined in the block layout") unless has_field?(field)
|
|
135
|
+
field_type = field_type field
|
|
136
|
+
# If field type is a model then we update the cached object
|
|
137
|
+
if field_type.is_a?(Class) && field_type < ActiveRecord::Base
|
|
138
|
+
|
|
139
|
+
# We read the object and assign it the attributes attributes.
|
|
140
|
+
# Since we use the read_field method it will take into account
|
|
141
|
+
# if the AR object needs to be build
|
|
142
|
+
read_field(field).assign_attributes value
|
|
143
|
+
|
|
144
|
+
# Even if the fields_info has not changed we need to store the
|
|
145
|
+
# modification, so an association may be saved in cascade (e.g.
|
|
146
|
+
# the translation of a block would not be saved if we don't force
|
|
147
|
+
# this)
|
|
148
|
+
fields_info_will_change!
|
|
149
|
+
else
|
|
150
|
+
# If it's not a model then we merge with the previous value
|
|
151
|
+
|
|
152
|
+
# when updating through an object (i.e. the page updates through
|
|
153
|
+
# nested attributes) fields_info[field.to_sym] = value doesn't
|
|
154
|
+
# work. Kudos to Rubo for this fix
|
|
155
|
+
self.fields_info = fields_info.nil? ?
|
|
156
|
+
{ field.to_sym => value } :
|
|
157
|
+
fields_info.merge(field.to_sym => value)
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# This method duplicates a field and stores its value.
|
|
164
|
+
#
|
|
165
|
+
# It takes into account that the field may be an AR object and updates
|
|
166
|
+
# the cached objects.
|
|
167
|
+
#
|
|
168
|
+
# We have different options of duplication depending on the field's
|
|
169
|
+
# configuration:
|
|
170
|
+
#
|
|
171
|
+
# * duplication: It's the default behaviour. It just performs a dup
|
|
172
|
+
# of the field and expects the attached object to implement dup in
|
|
173
|
+
# a proper way.
|
|
174
|
+
#
|
|
175
|
+
# * nullify: It doesn't dup the field, it empties it. It's useful for
|
|
176
|
+
# objects we don't want to duplicate, like images in S3 (it can
|
|
177
|
+
# raise a timeout exception when duplicating).
|
|
178
|
+
#
|
|
179
|
+
# * link: It doesn't dup the field but stores the same object. It's
|
|
180
|
+
# useful in Active Record fields so we can store the same id and
|
|
181
|
+
# not creating a duplicate of the object (e.g. if we have a block
|
|
182
|
+
# with a related post we don't want the post to be duplicated)
|
|
183
|
+
def duplicate_field field
|
|
184
|
+
field_type = field_type field
|
|
185
|
+
field_value = read_field(field)
|
|
186
|
+
|
|
187
|
+
dupped_value = case layout_config.field(field)[:duplicate]
|
|
188
|
+
# When dupping we just dp the object and expect it has the right
|
|
189
|
+
# behaviour. If it's nil we save nil (you can't dup NilClass)
|
|
190
|
+
when :dup
|
|
191
|
+
field_value.nil? ? nil : field_value.dup
|
|
192
|
+
# When nullifying we return nil
|
|
193
|
+
when :nullify
|
|
194
|
+
nil
|
|
195
|
+
when :link
|
|
196
|
+
field_value
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if field_type.is_a?(Class) && field_type < ActiveRecord::Base
|
|
200
|
+
# We save in the objects cache the dupped object
|
|
201
|
+
@cached_objects[field.to_sym] = dupped_value
|
|
202
|
+
# and then we store the new id in the fields_info hash
|
|
203
|
+
fields_info["#{field}_id".to_sym] =
|
|
204
|
+
dupped_value.nil? ? nil : dupped_value.id
|
|
205
|
+
else
|
|
206
|
+
# else we just dup it and save it into fields_info.
|
|
207
|
+
fields_info[field.to_sym] = dupped_value
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
##
|
|
212
|
+
# Saves every related object from the objects cache
|
|
213
|
+
def save_related_objects
|
|
214
|
+
# Now we save each activerecord related object
|
|
215
|
+
@cached_objects.each do |field, object|
|
|
216
|
+
# Notice that we don't care if the object is actually saved.
|
|
217
|
+
#
|
|
218
|
+
# We don't care because there may be some cases where no real
|
|
219
|
+
# information is sent to an object but something is sent (i.e. the
|
|
220
|
+
# locale in a new Globalize translation) and then the object is
|
|
221
|
+
# created empty.
|
|
222
|
+
#
|
|
223
|
+
# When this happens if we save! the object an error is thrown and
|
|
224
|
+
# we can't leave the object blank
|
|
225
|
+
if object.is_a?(ActiveRecord::Base) && object.save
|
|
226
|
+
fields_info["#{field}_id".to_sym] = object.id
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
##
|
|
232
|
+
# In this missing method we check wether we're asking for one field
|
|
233
|
+
# in which case we will read or write it.
|
|
234
|
+
#
|
|
235
|
+
# If there's no field we just let it go to super.
|
|
236
|
+
def method_missing(m, *args, &block)
|
|
237
|
+
# We get the name of the field stripping out the '=' for writers
|
|
238
|
+
field = m.to_s
|
|
239
|
+
write_accessor = field.ends_with? '='
|
|
240
|
+
field.gsub!(/\=$/, '')
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# If we don't have this field then we send it to super and pry
|
|
244
|
+
if field == 'layout' || !has_field?(field)
|
|
245
|
+
super
|
|
246
|
+
# If this field exists, and it's not translated, then we do whatever
|
|
247
|
+
# we need to do
|
|
248
|
+
elsif !layout_config.field(field)[:translated]
|
|
249
|
+
write_accessor ?
|
|
250
|
+
write_field(field, args.first) :
|
|
251
|
+
read_field(field.to_sym)
|
|
252
|
+
|
|
253
|
+
# If it's translated but we are not in the translation (we check
|
|
254
|
+
# this by checking if we have translations) then we use the
|
|
255
|
+
# default translation to obtain it
|
|
256
|
+
elsif !self.is_translation? &&
|
|
257
|
+
layout_config.field(field)[:translated]
|
|
258
|
+
|
|
259
|
+
# When we are creating the block we still have no translation
|
|
260
|
+
# and we need to fill the layout. Otherwise no write or read
|
|
261
|
+
# field will work
|
|
262
|
+
translation.layout = self.layout
|
|
263
|
+
|
|
264
|
+
write_accessor ?
|
|
265
|
+
translation.write_field(field, args.first) :
|
|
266
|
+
translation.read_field(field.to_sym)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
##
|
|
271
|
+
# When we are assigning attributes (this method is called in new,
|
|
272
|
+
# create...) we must split those fields from our current layout and
|
|
273
|
+
# those who are not (they must be attributes).
|
|
274
|
+
#
|
|
275
|
+
# Attributes are processed the usual way and fields are written later
|
|
276
|
+
def assign_attributes new_attributes
|
|
277
|
+
fields = []
|
|
278
|
+
|
|
279
|
+
set_blank_fields
|
|
280
|
+
|
|
281
|
+
# We get the layout
|
|
282
|
+
new_layout = new_attributes[:layout] || new_attributes['layout']
|
|
283
|
+
self.layout = new_layout unless new_layout.nil?
|
|
284
|
+
|
|
285
|
+
Rails.logger.info "Searching #{new_attributes.keys.inspect} fields"+
|
|
286
|
+
"in #{self.layout} layout"
|
|
287
|
+
|
|
288
|
+
# And now separate fields and attributes
|
|
289
|
+
fields = new_attributes.select{|k, _| has_field? k }.symbolize_keys
|
|
290
|
+
# Now we filter those fields we must not manage because we are (or
|
|
291
|
+
# not) in a translation. I.e: if we have a translated field, but we
|
|
292
|
+
# are not in a translation then we let the translated field go and
|
|
293
|
+
# not manage it here
|
|
294
|
+
fields.select!{|k, _| layout_config.field(k)[:translated] == is_translation? }
|
|
295
|
+
|
|
296
|
+
# We purge the fields from the attributes
|
|
297
|
+
new_attributes.reject!{|k, _| fields.has_key? k }
|
|
298
|
+
|
|
299
|
+
# If we have translations we're going to need the layout in their
|
|
300
|
+
# attributes too so they can validate the fields.
|
|
301
|
+
# This is actually only neccesary when creating the translations,
|
|
302
|
+
# but we can afford to send the layout always to the translations
|
|
303
|
+
if new_attributes.has_key? :translations_attributes
|
|
304
|
+
new_attributes[:translations_attributes].each do |translation|
|
|
305
|
+
translation[:layout] = self.layout
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
super(new_attributes)
|
|
310
|
+
|
|
311
|
+
Rails.logger.info "Writing #{fields.inspect} to #{self.layout} block"
|
|
312
|
+
|
|
313
|
+
fields.each do |field_name, value|
|
|
314
|
+
self.write_field field_name, value
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
##
|
|
319
|
+
# It cleans the objects cache and executes the default behaviour.
|
|
320
|
+
def reload *args
|
|
321
|
+
@cached_objects = {}
|
|
322
|
+
super
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
##
|
|
326
|
+
# Overwriting duplication method. This method relies on super for any
|
|
327
|
+
# default behaviour, then duplicate the fields just as their
|
|
328
|
+
# configuration demands and then allow the object to custimize the
|
|
329
|
+
# duplication through the duplicate_self method.
|
|
330
|
+
def dup
|
|
331
|
+
new_self = super
|
|
332
|
+
|
|
333
|
+
# Now we recover all the fields that must be duplicated here
|
|
334
|
+
fields_to_duplicate.keys.each do |field_to_duplicate|
|
|
335
|
+
new_self.duplicate_field field_to_duplicate
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# And allow the class itself to append some behaviour
|
|
339
|
+
duplicate_self new_self
|
|
340
|
+
|
|
341
|
+
new_self
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
##
|
|
345
|
+
# This method allows us to introduce some custom duplication for each
|
|
346
|
+
# class that includes the concern.
|
|
347
|
+
#
|
|
348
|
+
# Basic behaviour is... doing nothing
|
|
349
|
+
def duplicate_self new_self
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
##
|
|
353
|
+
# This method returns a list of the fields to duplicate in this
|
|
354
|
+
# object. In the concern we will use all the fields and if the classes
|
|
355
|
+
# need to modify it they will.
|
|
356
|
+
#
|
|
357
|
+
# Notice that if the behaviour is that when duplicating the field is
|
|
358
|
+
# nullfied we will return the field here and the duplicate_field will
|
|
359
|
+
# manage it properly.
|
|
360
|
+
def fields_to_duplicate
|
|
361
|
+
fields_configuration
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
##
|
|
365
|
+
# When dupping we need to overwrite the cached_objects attribute.
|
|
366
|
+
# Otherwise the cached_object from the original object would start to
|
|
367
|
+
# be populated with the objects of the dupped one.
|
|
368
|
+
def initialize_dup(other)
|
|
369
|
+
@cached_objects = {}
|
|
370
|
+
super
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
private
|
|
374
|
+
|
|
375
|
+
##
|
|
376
|
+
# Initializes both the fields_info hash and the objects cache.
|
|
377
|
+
def set_blank_fields
|
|
378
|
+
@fields_info ||= {}
|
|
379
|
+
@cached_objects ||= {}
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|