dynamic_layouts 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e0b9e03aa572a8f7d1109b8a5b712d00fbd9c6b9ece7dd130d95a69043919de
4
- data.tar.gz: e595057c5c95c165a2823e666c4d9bd6ed5eace6308bbe6c310b5ffe7404bf20
3
+ metadata.gz: fa58eac2c16e862711740275aec6a38fe3a27124b511ddb9bbac54d31a9500da
4
+ data.tar.gz: 218b984c4b32fa690bf5ec1212cc0218aacffa34f071c104a95cce490823b996
5
5
  SHA512:
6
- metadata.gz: 283631224c1c912cdda718d4ab769c2a87960619dac6177660d5dcc3ecb8e9fb2634947277cce17bf717821a5ab18e5ef251389cb7ec0bb43223559afab1aa3f
7
- data.tar.gz: 6bead906043a49945de821c163b09c38cbee7b07231bd87fe56972a5aa247624eee1211e225a3f5f327edbd4d8d21edc5a25e4f50fec11505134e24db6618fff
6
+ metadata.gz: 021d57dc7a614d963784f9b5fa5332e04d79ab3df950f674cbdf1cc9ac0db5775c341489b96952e576f5106ff3985add018c90aa414512d503e118a2ba2284cc
7
+ data.tar.gz: ca58426ba01060c493aaffc9843ade0f21319f66856a72975a629b2238ccae377e0cb9b32ae77e50c4b818884669d575cfa83ee6859d6b489667bd9f22b3e8ac
data/README.md CHANGED
@@ -1,39 +1,330 @@
1
- # DynamicLayouts
1
+ # Dynamic Layouts
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ A Ruby on Rails gem for building flexible, database-driven UI layouts. Dynamic Layouts allows you to define and manage complex form layouts, tables, dashboards, and markdown content through a hierarchical structure stored in your database.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dynamic_layouts`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## Features
6
+
7
+ - **Grid Layouts**: Create responsive form layouts with various field types
8
+ - **Table Layouts**: Build dynamic tables with configurable columns
9
+ - **Dashboard Widgets**: Design dashboard interfaces with customizable widgets
10
+ - **Markdown Elements**: Add rich text content and documentation
11
+ - **Layout Overrides**: Programmatically customize layouts at runtime
12
+ - **Custom Transformers**: Implement business logic to modify layouts dynamically
13
+ - **Easy Setup**: Rails generator creates all necessary migrations and models
14
+
15
+ ## Requirements
16
+
17
+ - Ruby >= 3.2.0
18
+ - Rails >= 6.1
6
19
 
7
20
  ## Installation
8
21
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'dynamic_layouts'
26
+ ```
27
+
28
+ Then execute:
29
+
30
+ ```bash
31
+ bundle install
32
+ ```
33
+
34
+ Run the generator to create migrations and models:
10
35
 
11
- Install the gem and add to the application's Gemfile by executing:
36
+ ```bash
37
+ rails generate dynamic_layouts:install
38
+ rails db:migrate
39
+ ```
12
40
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
41
+ This will create the following database tables and models:
42
+ - `dynamic_layouts`
43
+ - `dynamic_layout_containers`
44
+ - `dynamic_layout_sections`
45
+ - `dynamic_layout_grid_fields`
46
+ - `dynamic_layout_table_configs`
47
+ - `dynamic_layout_table_columns`
48
+ - `dynamic_layout_dashboard_widgets`
49
+ - `dynamic_layout_markdown_elements`
14
50
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
51
+ ## Architecture
16
52
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
53
+ Dynamic Layouts uses a hierarchical structure:
54
+
55
+ ```
56
+ Layout
57
+ └── Container
58
+ └── Section
59
+ └── Elements (Grid Fields, Table Columns, Dashboard Widgets, or Markdown)
60
+ ```
61
+
62
+ - **Layout**: The top-level container for a complete UI layout
63
+ - **Container**: Groups related sections together
64
+ - **Section**: Defines a specific layout type (grid, table, dashboard, or markdown)
65
+ - **Elements**: Individual components within a section
18
66
 
19
67
  ## Usage
20
68
 
21
- TODO: Write usage instructions here
69
+ ### Basic Layout Building
70
+
71
+ Build and retrieve a layout:
72
+
73
+ ```ruby
74
+ # Simple build
75
+ layout = DynamicLayouts::DynamicLayoutBuilder.build('user_form')
76
+
77
+ # Build with transformation
78
+ layout = DynamicLayouts::DynamicLayoutBuilder.build(
79
+ 'user_form',
80
+ record: @user,
81
+ controller: self,
82
+ user: current_user
83
+ )
84
+ ```
85
+
86
+ The builder will:
87
+ 1. Find the layout by name
88
+ 2. Eager load all associated sections and elements
89
+ 3. Serialize the layout to a hash structure
90
+ 4. Apply transformations if a transformer is provided
91
+
92
+ ### Custom Transformers
93
+
94
+ Create custom transformers to modify layouts based on business logic:
95
+
96
+ ```ruby
97
+ class UserLayoutTransformer < DynamicLayouts::DynamicLayoutTransformer
98
+ def transform
99
+ # Hide email field for non-admin users
100
+ if @current_user && !@current_user.admin?
101
+ hide_field('contact_info', :email)
102
+ end
103
+
104
+ # Make fields read-only for archived records
105
+ if @record&.archived?
106
+ make_section_readonly('personal_details')
107
+ end
108
+
109
+ @dynamic_layout
110
+ end
111
+
112
+ private
113
+
114
+ def hide_field(section_slug, field_attr)
115
+ section = find_section(section_slug)
116
+ return unless section
117
+
118
+ field = section[:elements].find { |f| f[:attr] == field_attr.to_s }
119
+ field[:hidden] = true if field
120
+ end
121
+
122
+ def make_section_readonly(section_slug)
123
+ section = find_section(section_slug)
124
+ return unless section
125
+
126
+ section[:writable] = false
127
+ end
128
+
129
+ def find_section(slug)
130
+ @dynamic_layout[:containers].each do |container|
131
+ section = container[:sections].find { |s| s[:slug] == slug.to_s }
132
+ return section if section
133
+ end
134
+ nil
135
+ end
136
+ end
137
+ ```
138
+
139
+ The transformer will be automatically discovered if it follows the naming convention: `{Controller}LayoutTransformer`.
140
+
141
+ ### Layout Overrides
142
+
143
+ Override layout configuration at runtime:
144
+
145
+ ```ruby
146
+ layout = DynamicLayouts::DynamicLayoutBuilder.build('user_form')
147
+
148
+ overrides = {
149
+ contact_info: {
150
+ writable: false,
151
+ collapsed: true,
152
+ elements: {
153
+ email: {
154
+ required: true,
155
+ tooltip: 'A valid email address is required'
156
+ },
157
+ phone: {
158
+ hidden: true
159
+ }
160
+ }
161
+ },
162
+ personal_details: {
163
+ elements: {
164
+ first_name: {
165
+ label: 'First Name (Required)',
166
+ required: true
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ DynamicLayouts::DynamicLayoutService.override_layout_config(layout, overrides)
173
+ ```
174
+
175
+ ### Grid Layout
176
+
177
+ Grid layouts are ideal for forms with various field types:
178
+
179
+ ```ruby
180
+ # In your layout configuration
181
+ section = dynamic_layout.containers.first.sections.create!(
182
+ name: 'Contact Information',
183
+ slug: 'contact_info',
184
+ layout: 'grid',
185
+ position: 1,
186
+ writable: true
187
+ )
188
+
189
+ # Add fields
190
+ section.grid_fields.create!(
191
+ label: 'Email',
192
+ attr: 'email',
193
+ attr_type: 'email',
194
+ required: true,
195
+ position: 1
196
+ )
197
+
198
+ section.grid_fields.create!(
199
+ label: 'Phone',
200
+ attr: 'phone',
201
+ attr_type: 'tel',
202
+ position: 2
203
+ )
204
+ ```
205
+
206
+ ### Table Layout
207
+
208
+ Table layouts display data in columns:
209
+
210
+ ```ruby
211
+ section = dynamic_layout.containers.first.sections.create!(
212
+ name: 'Users List',
213
+ slug: 'users_list',
214
+ layout: 'table',
215
+ position: 1
216
+ )
217
+
218
+ table_config = section.table_config
219
+ table_config.table_columns.create!(
220
+ header: 'Name',
221
+ attr: 'name',
222
+ position: 1
223
+ )
224
+
225
+ table_config.table_columns.create!(
226
+ header: 'Email',
227
+ attr: 'email',
228
+ position: 2
229
+ )
230
+ ```
231
+
232
+ ### Dashboard Layout
233
+
234
+ Dashboard layouts create widget-based interfaces:
235
+
236
+ ```ruby
237
+ section = dynamic_layout.containers.first.sections.create!(
238
+ name: 'Analytics Dashboard',
239
+ slug: 'analytics',
240
+ layout: 'dashboard',
241
+ position: 1
242
+ )
243
+
244
+ section.dashboard_widgets.create!(
245
+ label: 'Total Users',
246
+ slug: 'total_users',
247
+ widget_type: 'stat',
248
+ position: 1
249
+ )
250
+
251
+ section.dashboard_widgets.create!(
252
+ label: 'Revenue Chart',
253
+ slug: 'revenue_chart',
254
+ widget_type: 'chart',
255
+ position: 2
256
+ )
257
+ ```
258
+
259
+ ### Markdown Layout
260
+
261
+ Markdown layouts add documentation and rich text:
262
+
263
+ ```ruby
264
+ section = dynamic_layout.containers.first.sections.create!(
265
+ name: 'Instructions',
266
+ slug: 'instructions',
267
+ layout: 'markdown',
268
+ position: 1
269
+ )
270
+
271
+ section.markdown_elements.create!(
272
+ content: '# Welcome
273
+ position: 1
274
+ )
275
+ ```
276
+
277
+ ### Field Options
278
+
279
+ #### Dropdown Fields
280
+
281
+ ```ruby
282
+ grid_field.update!(
283
+ attr_type: 'dropdown',
284
+ options: 'mode:static;values:active=Active,inactive=Inactive,pending=Pending'
285
+ )
286
+
287
+ # Or with associations
288
+ grid_field.update!(
289
+ attr_type: 'dropdown',
290
+ options: 'mode:association;class:Department;scopes:active;label:name;value:id'
291
+ )
292
+ ```
293
+
294
+ #### Autocomplete Fields
295
+
296
+ ```ruby
297
+ grid_field.update!(
298
+ attr_type: 'autocomplete',
299
+ options: 'mode:static;values:New York,Los Angeles,Chicago,Houston'
300
+ )
301
+ ```
302
+
303
+ #### Rich Text Fields
304
+
305
+ ```ruby
306
+ grid_field.update!(
307
+ attr_type: 'rich-text',
308
+ options: 'placeholder_attrs:first_name,last_name,email'
309
+ )
310
+ ```
22
311
 
23
312
  ## Development
24
313
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
314
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
26
315
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
316
+ ## Testing
28
317
 
29
- ## Contributing
318
+ Run the test suite:
30
319
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dynamic_layouts. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/dynamic_layouts/blob/main/CODE_OF_CONDUCT.md).
320
+ ```bash
321
+ bundle exec rspec
322
+ ```
32
323
 
33
324
  ## License
34
325
 
35
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
326
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
36
327
 
37
- ## Code of Conduct
328
+ ## Contributing
38
329
 
39
- Everyone interacting in the DynamicLayouts project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/dynamic_layouts/blob/main/CODE_OF_CONDUCT.md).
330
+ Bug reports and pull requests are welcome on GitHub at https://github.com/CodeTectonics/dynamic-layouts.
@@ -174,8 +174,9 @@ module DynamicLayouts
174
174
  res.send(scope)
175
175
  end
176
176
 
177
- data_source.where("#{options[:attribute]} IS NOT NULL AND #{options[:attribute]} != ''")
178
- .select("LOWER(TRIM(#{options[:attribute]})) AS formatted_value")
177
+ column = Arel.sql(ActiveRecord::Base.connection.quote_column_name(options[:attribute]))
178
+ data_source.where("#{column} IS NOT NULL AND #{column} != ''")
179
+ .select("LOWER(TRIM(#{column})) AS formatted_value")
179
180
  .order(:formatted_value).distinct
180
181
  .map do |item|
181
182
  { label: item.formatted_value.capitalize, value: item.formatted_value.capitalize }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamicLayouts
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_layouts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Harbison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-01 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails