iron-cms 0.1.2 → 0.1.3

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.
@@ -4,6 +4,14 @@ module Iron
4
4
 
5
5
  has_many :field_definitions, -> { ranked }, as: :schemable, dependent: :destroy
6
6
 
7
+ has_and_belongs_to_many :referencing_field_definitions,
8
+ class_name: "::Iron::FieldDefinition",
9
+ foreign_key: "block_definition_id",
10
+ association_foreign_key: "field_definition_id",
11
+ join_table: "iron_block_definitions_field_definitions"
12
+
13
+ before_destroy { referencing_field_definitions.clear }
14
+
7
15
  validates :name, presence: true
8
16
  validates :handle, presence: true
9
17
  end
@@ -11,6 +11,14 @@ module Iron
11
11
 
12
12
  has_many :entries, -> { extending FieldQueryable }, dependent: :destroy
13
13
 
14
+ has_and_belongs_to_many :referencing_field_definitions,
15
+ class_name: "::Iron::FieldDefinition",
16
+ foreign_key: "content_type_id",
17
+ association_foreign_key: "field_definition_id",
18
+ join_table: "iron_content_types_field_definitions"
19
+
20
+ before_destroy { referencing_field_definitions.clear }
21
+
14
22
  class << self
15
23
  def classify_type(type)
16
24
  "Iron::ContentTypes::#{type}"
@@ -5,7 +5,7 @@ module Iron
5
5
 
6
6
  class_methods do
7
7
  def csv_headers
8
- %w[parent_type parent_handle handle name type rank metadata supported_block_definitions]
8
+ %w[parent_type parent_handle handle name type rank metadata supported_block_definitions supported_content_types]
9
9
  end
10
10
 
11
11
  def csv_scope
@@ -22,15 +22,21 @@ module Iron
22
22
  type.demodulize.underscore,
23
23
  rank,
24
24
  metadata.to_json,
25
- export_supported_block_definitions
25
+ export_supported_block_definitions,
26
+ export_supported_content_types
26
27
  ]
27
28
  end
28
29
 
29
30
  private
30
31
 
31
- def export_supported_block_definitions
32
- return nil unless respond_to?(:supported_block_definitions)
33
- supported_block_definitions.pluck(:handle).join("|")
34
- end
32
+ def export_supported_block_definitions
33
+ return nil unless respond_to?(:supported_block_definitions)
34
+ supported_block_definitions.pluck(:handle).join("|")
35
+ end
36
+
37
+ def export_supported_content_types
38
+ return nil unless respond_to?(:supported_content_types)
39
+ supported_content_types.pluck(:handle).join("|")
40
+ end
35
41
  end
36
42
  end
@@ -0,0 +1,59 @@
1
+ module Iron
2
+ class SchemaImporter::ImportStrategy
3
+ def import_block_definition(row)
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def import_content_type(row)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def import_field_definition(row)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ protected
16
+
17
+ def find_parent(parent_type, parent_handle)
18
+ case parent_type
19
+ when "content_type"
20
+ ContentType.find_by(handle: parent_handle)
21
+ when "block"
22
+ BlockDefinition.find_by(handle: parent_handle)
23
+ end
24
+ end
25
+
26
+ def content_type_class(type)
27
+ case type
28
+ when "single" then ContentTypes::Single
29
+ when "collection" then ContentTypes::Collection
30
+ else
31
+ raise "Invalid content type: #{type}. Must be 'single' or 'collection'"
32
+ end
33
+ end
34
+
35
+ def field_definition_class(type)
36
+ "Iron::FieldDefinitions::#{type.camelize}".constantize
37
+ end
38
+
39
+ def parse_metadata(metadata_string)
40
+ return {} if metadata_string.blank?
41
+
42
+ JSON.parse(metadata_string)
43
+ rescue JSON::ParserError
44
+ {}
45
+ end
46
+
47
+ def parse_supported_block_definitions(supported_handles_string)
48
+ return BlockDefinition.none unless supported_handles_string.present?
49
+
50
+ BlockDefinition.where(handle: supported_handles_string.split("|"))
51
+ end
52
+
53
+ def parse_supported_content_types(supported_handles_string)
54
+ return ContentType.none unless supported_handles_string.present?
55
+
56
+ ContentType.where(handle: supported_handles_string.split("|"))
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ module Iron
2
+ class SchemaImporter::MergeStrategy < SchemaImporter::ImportStrategy
3
+ def import_block_definition(row)
4
+ definition = BlockDefinition.find_or_initialize_by(handle: row["handle"])
5
+ definition.update!(
6
+ name: row["name"],
7
+ description: row["description"]
8
+ )
9
+ end
10
+
11
+ def import_content_type(row)
12
+ content_type = ContentType.find_or_initialize_by(handle: row["handle"])
13
+
14
+ content_type = content_type.becomes!(content_type_class(row["type"]))
15
+ content_type.update!(
16
+ name: row["name"],
17
+ description: row["description"],
18
+ icon: row["icon"],
19
+ web_publishing_enabled: row["web_publishing_enabled"] == "true",
20
+ base_path: row["base_path"]
21
+ )
22
+ end
23
+
24
+ def import_field_definition(row)
25
+ raise "Field type is required" unless row["type"].present?
26
+ return unless row["parent_type"].present? && row["parent_handle"].present? && row["handle"].present?
27
+
28
+ parent = find_parent(row["parent_type"], row["parent_handle"])
29
+ return unless parent
30
+
31
+ field_def = parent.field_definitions.find_or_initialize_by(handle: row["handle"])
32
+ field_def = field_def.becomes!(field_definition_class(row["type"]))
33
+
34
+ attributes = {
35
+ name: row["name"],
36
+ rank: row["rank"],
37
+ metadata: parse_metadata(row["metadata"])
38
+ }
39
+
40
+ if field_def.is_a?(FieldDefinitions::Block) || field_def.is_a?(FieldDefinitions::BlockList)
41
+ attributes[:supported_block_definition_ids] = parse_supported_block_definitions(row["supported_block_definitions"]).pluck(:id)
42
+ end
43
+
44
+ if field_def.is_a?(FieldDefinitions::Reference) || field_def.is_a?(FieldDefinitions::ReferenceList)
45
+ attributes[:supported_content_type_ids] = parse_supported_content_types(row["supported_content_types"]).pluck(:id)
46
+ end
47
+
48
+ field_def.assign_attributes(attributes)
49
+ field_def.save!
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ module Iron
2
+ class SchemaImporter::ReplaceStrategy < SchemaImporter::ImportStrategy
3
+ def import_block_definition(row)
4
+ BlockDefinition.create!(
5
+ handle: row["handle"],
6
+ name: row["name"],
7
+ description: row["description"]
8
+ )
9
+ end
10
+
11
+ def import_content_type(row)
12
+ content_type_class(row["type"]).create!(
13
+ handle: row["handle"],
14
+ name: row["name"],
15
+ description: row["description"],
16
+ icon: row["icon"],
17
+ web_publishing_enabled: row["web_publishing_enabled"] == "true",
18
+ base_path: row["base_path"]
19
+ )
20
+ end
21
+
22
+ def import_field_definition(row)
23
+ raise "Field type is required" unless row["type"].present?
24
+ return unless row["parent_type"].present? && row["parent_handle"].present? && row["handle"].present?
25
+
26
+ parent = find_parent(row["parent_type"], row["parent_handle"])
27
+ return unless parent
28
+
29
+ parent.field_definitions.find_by(handle: row["handle"])&.destroy!
30
+
31
+ klass = field_definition_class(row["type"])
32
+ attributes = {
33
+ schemable: parent,
34
+ handle: row["handle"],
35
+ name: row["name"],
36
+ rank: row["rank"],
37
+ metadata: parse_metadata(row["metadata"])
38
+ }
39
+
40
+ if klass <= FieldDefinitions::Block || klass <= FieldDefinitions::BlockList
41
+ attributes[:supported_block_definition_ids] = parse_supported_block_definitions(row["supported_block_definitions"]).pluck(:id)
42
+ end
43
+
44
+ if klass <= FieldDefinitions::Reference || klass <= FieldDefinitions::ReferenceList
45
+ attributes[:supported_content_type_ids] = parse_supported_content_types(row["supported_content_types"]).pluck(:id)
46
+ end
47
+
48
+ klass.create!(attributes)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ module Iron
2
+ class SchemaImporter::SafeStrategy < SchemaImporter::ImportStrategy
3
+ def import_block_definition(row)
4
+ return if BlockDefinition.exists?(handle: row["handle"])
5
+
6
+ BlockDefinition.create!(
7
+ handle: row["handle"],
8
+ name: row["name"],
9
+ description: row["description"]
10
+ )
11
+ end
12
+
13
+ def import_content_type(row)
14
+ return if ContentType.exists?(handle: row["handle"])
15
+
16
+ content_type_class(row["type"]).create!(
17
+ handle: row["handle"],
18
+ name: row["name"],
19
+ description: row["description"],
20
+ icon: row["icon"],
21
+ web_publishing_enabled: row["web_publishing_enabled"] == "true",
22
+ base_path: row["base_path"]
23
+ )
24
+ end
25
+
26
+ def import_field_definition(row)
27
+ raise "Field type is required" unless row["type"].present?
28
+ return unless row["parent_type"].present? && row["parent_handle"].present? && row["handle"].present?
29
+
30
+ parent = find_parent(row["parent_type"], row["parent_handle"])
31
+ return unless parent
32
+
33
+ return if parent.field_definitions.exists?(handle: row["handle"])
34
+
35
+ klass = field_definition_class(row["type"])
36
+ attributes = {
37
+ schemable: parent,
38
+ handle: row["handle"],
39
+ name: row["name"],
40
+ rank: row["rank"],
41
+ metadata: parse_metadata(row["metadata"])
42
+ }
43
+
44
+ if klass <= FieldDefinitions::Block || klass <= FieldDefinitions::BlockList
45
+ attributes[:supported_block_definition_ids] = parse_supported_block_definitions(row["supported_block_definitions"]).pluck(:id)
46
+ end
47
+
48
+ if klass <= FieldDefinitions::Reference || klass <= FieldDefinitions::ReferenceList
49
+ attributes[:supported_content_type_ids] = parse_supported_content_types(row["supported_content_types"]).pluck(:id)
50
+ end
51
+
52
+ klass.create!(attributes)
53
+ end
54
+ end
55
+ end
@@ -11,16 +11,16 @@ module Iron
11
11
  end
12
12
 
13
13
  def initialize(file, mode: "merge")
14
+ raise ArgumentError, "Missing import file" unless file.present?
15
+ raise ArgumentError, "Invalid import mode: #{mode}. Must be one of: #{IMPORT_MODES.join(', ')}" unless IMPORT_MODES.include?(mode)
16
+
14
17
  @file = file
15
18
  @mode = mode.to_s
16
19
  @errors = []
17
-
18
- validate_mode!
20
+ @strategy = build_strategy(mode)
19
21
  end
20
22
 
21
23
  def import
22
- return ImportResult.new(false, errors) unless valid?
23
-
24
24
  ActiveRecord::Base.transaction do
25
25
  Account.clear_schema! if mode == "replace"
26
26
 
@@ -31,205 +31,45 @@ module Iron
31
31
  return ImportResult.new(false, errors)
32
32
  end
33
33
 
34
- import_block_definitions(archive["block_definitions.csv"])
35
- import_content_types(archive["content_types.csv"])
36
- import_field_definitions(archive["field_definitions.csv"])
37
-
38
- update_content_type_references(archive["content_types.csv"])
39
-
40
- ImportResult.new(true, [])
41
- end
42
- rescue StandardError => e
43
- errors << "Import failed: #{e.message}"
44
- ImportResult.new(false, errors)
45
- end
46
-
47
- private
48
-
49
- def valid?
50
- validate_file_presence
51
- errors.empty?
52
- end
53
-
54
- def validate_mode!
55
- unless IMPORT_MODES.include?(mode)
56
- raise ArgumentError, "Invalid import mode: #{mode}. Must be one of: #{IMPORT_MODES.join(', ')}"
57
- end
58
- end
59
-
60
- def validate_file_presence
61
- errors << "No file provided" unless file
62
- end
63
-
64
- def import_block_definitions(csv_content)
65
- return unless csv_content
66
-
67
- CSV.parse(csv_content, headers: true) do |row|
68
- handle = row["handle"]
69
- next unless handle.present?
70
-
71
- case mode
72
- when "merge"
73
- block_def = BlockDefinition.find_or_initialize_by(handle: handle)
74
- block_def.assign_attributes(
75
- name: row["name"],
76
- description: row["description"]
77
- )
78
- block_def.save!
79
- when "replace"
80
- BlockDefinition.create!(
81
- handle: handle,
82
- name: row["name"],
83
- description: row["description"]
84
- )
85
- when "safe"
86
- next if BlockDefinition.exists?(handle: handle)
87
-
88
- BlockDefinition.create!(
89
- handle: handle,
90
- name: row["name"],
91
- description: row["description"]
92
- )
34
+ if archive["block_definitions.csv"]
35
+ CSV.parse(archive["block_definitions.csv"], headers: true) do |row|
36
+ next unless row["handle"].present?
37
+ @strategy.import_block_definition(row)
93
38
  end
94
39
  end
95
- end
96
40
 
97
- def import_content_types(csv_content)
98
- return unless csv_content
99
-
100
- CSV.parse(csv_content, headers: true) do |row|
101
- handle = row["handle"]
102
- next unless handle.present?
103
-
104
- case mode
105
- when "merge"
106
- content_type = ContentType.find_or_initialize_by(handle: handle)
107
- update_content_type(content_type, row)
108
- when "replace"
109
- content_type = build_content_type(row)
110
- content_type.save!
111
- when "safe"
112
- next if ContentType.exists?(handle: handle)
113
-
114
- content_type = build_content_type(row)
115
- content_type.save!
41
+ if archive["content_types.csv"]
42
+ CSV.parse(archive["content_types.csv"], headers: true) do |row|
43
+ next unless row["handle"].present?
44
+ @strategy.import_content_type(row)
116
45
  end
117
46
  end
118
- end
119
-
120
- def import_field_definitions(csv_content)
121
- return unless csv_content
122
-
123
- CSV.parse(csv_content, headers: true) do |row|
124
- parent_type = row["parent_type"]
125
- parent_handle = row["parent_handle"]
126
- handle = row["handle"]
127
47
 
128
- next unless parent_type.present? && parent_handle.present? && handle.present?
129
-
130
- parent = find_parent(parent_type, parent_handle)
131
- next unless parent
132
-
133
- case mode
134
- when "merge"
135
- field_def = parent.field_definitions.find_or_initialize_by(handle: handle)
136
- update_field_definition(field_def, row)
137
- when "replace"
138
- field_def = build_field_definition(parent, row)
139
- field_def.save!
140
- when "safe"
141
- next if parent.field_definitions.exists?(handle: handle)
142
-
143
- field_def = build_field_definition(parent, row)
144
- field_def.save!
48
+ if archive["field_definitions.csv"]
49
+ CSV.parse(archive["field_definitions.csv"], headers: true) do |row|
50
+ @strategy.import_field_definition(row)
145
51
  end
146
52
  end
147
- end
148
-
149
- def find_parent(parent_type, parent_handle)
150
- case parent_type
151
- when "content_type"
152
- ContentType.find_by(handle: parent_handle)
153
- when "block"
154
- BlockDefinition.find_by(handle: parent_handle)
155
- end
156
- end
157
-
158
- def build_content_type(row)
159
- klass = case row["type"]
160
- when "single" then ContentTypes::Single
161
- when "collection" then ContentTypes::Collection
162
- else ContentType
163
- end
164
-
165
- klass.new(
166
- handle: row["handle"],
167
- name: row["name"],
168
- description: row["description"],
169
- icon: row["icon"],
170
- web_publishing_enabled: row["web_publishing_enabled"] == "true",
171
- base_path: row["base_path"]
172
- )
173
- end
174
-
175
- def update_content_type(content_type, row)
176
- content_type.assign_attributes(
177
- name: row["name"],
178
- description: row["description"],
179
- icon: row["icon"],
180
- web_publishing_enabled: row["web_publishing_enabled"] == "true",
181
- base_path: row["base_path"]
182
- )
183
-
184
- # Update type if changed
185
- if row["type"] && content_type.type != "Iron::ContentTypes::#{row['type'].camelize}"
186
- content_type.type = "Iron::ContentTypes::#{row['type'].camelize}"
187
- end
188
-
189
- content_type.save!
190
- end
191
-
192
- def build_field_definition(parent, row)
193
- klass = "Iron::FieldDefinitions::#{row['type'].camelize}".constantize
194
53
 
195
- field_def = klass.new(
196
- schemable: parent,
197
- handle: row["handle"],
198
- name: row["name"],
199
- rank: row["rank"],
200
- metadata: parse_metadata(row["metadata"])
201
- )
202
-
203
- # Set supported block definitions before saving
204
- set_supported_block_definitions(field_def, row["supported_block_definitions"])
54
+ update_content_type_references(archive["content_types.csv"])
205
55
 
206
- field_def
56
+ ImportResult.new(true, [])
207
57
  end
58
+ rescue StandardError => e
59
+ errors << "Import failed: #{e.message}"
60
+ ImportResult.new(false, errors)
61
+ end
208
62
 
209
- def update_field_definition(field_def, row)
210
- field_def.assign_attributes(
211
- name: row["name"],
212
- rank: row["rank"],
213
- metadata: parse_metadata(row["metadata"])
214
- )
63
+ private
215
64
 
216
- # Update type if changed
217
- if row["type"] && field_def.type != "Iron::FieldDefinitions::#{row['type'].camelize}"
218
- field_def.type = "Iron::FieldDefinitions::#{row['type'].camelize}"
65
+ def build_strategy(mode)
66
+ case mode.to_s
67
+ when "merge" then SchemaImporter::MergeStrategy.new
68
+ when "replace" then SchemaImporter::ReplaceStrategy.new
69
+ when "safe" then SchemaImporter::SafeStrategy.new
70
+ else
71
+ raise ArgumentError, "Invalid import mode: #{mode}"
219
72
  end
220
-
221
- # Set supported block definitions before saving
222
- set_supported_block_definitions(field_def, row["supported_block_definitions"])
223
-
224
- field_def.save!
225
- end
226
-
227
- def parse_metadata(metadata_string)
228
- return {} if metadata_string.blank?
229
-
230
- JSON.parse(metadata_string)
231
- rescue JSON::ParserError
232
- {}
233
73
  end
234
74
 
235
75
  def update_content_type_references(content_types_csv)
@@ -252,16 +92,6 @@ module Iron
252
92
  end
253
93
  end
254
94
 
255
- def set_supported_block_definitions(field_def, supported_handles_string)
256
- return unless supported_handles_string.present?
257
- return unless field_def.respond_to?(:supported_block_definitions)
258
-
259
- handles = supported_handles_string.split("|")
260
- block_definitions = BlockDefinition.where(handle: handles)
261
-
262
- field_def.supported_block_definitions = block_definitions
263
- end
264
-
265
95
  class ImportResult
266
96
  attr_reader :errors
267
97
 
data/lib/iron/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Iron
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/iron-cms.rb CHANGED
@@ -1 +1 @@
1
- require "iron"
1
+ require "iron"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iron-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Massimo De Marchi
@@ -291,6 +291,10 @@ files:
291
291
  - app/models/iron/schema_archive.rb
292
292
  - app/models/iron/schema_exporter.rb
293
293
  - app/models/iron/schema_importer.rb
294
+ - app/models/iron/schema_importer/import_strategy.rb
295
+ - app/models/iron/schema_importer/merge_strategy.rb
296
+ - app/models/iron/schema_importer/replace_strategy.rb
297
+ - app/models/iron/schema_importer/safe_strategy.rb
294
298
  - app/models/iron/session.rb
295
299
  - app/models/iron/user.rb
296
300
  - app/models/iron/user/role.rb