gridy 0.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.
@@ -0,0 +1,117 @@
1
+ @import "./gridy/open-props.css";
2
+ @import "./gridy/open-props/normalize.css";
3
+ @import "./gridy/open-props/buttons.css";
4
+
5
+ .gridy {
6
+ font-size: 14px;
7
+
8
+ .search-form-container {
9
+ padding: var(--size-2);
10
+ margin-bottom: var(--size-2);
11
+
12
+ form {
13
+ width: 100%;
14
+ }
15
+
16
+ input[type="search"] {
17
+ flex-grow: 1;
18
+ padding: var(--size-1) var(--size-2);
19
+ border-radius: var(--radius-2);
20
+ border: 1px solid var(--border-2);
21
+ }
22
+ }
23
+
24
+ .table-container {
25
+ max-width: 100%;
26
+ /* overflow-x: auto; */
27
+
28
+ table {
29
+ width: 100%;
30
+ border-radius: var(--radius-2);
31
+
32
+ thead {
33
+ position: sticky;
34
+ top: 0;
35
+
36
+ :where(tr:first-child th:first-child) {
37
+ border-top-left-radius: var(--radius-2);
38
+ }
39
+ }
40
+
41
+ th,
42
+ td {
43
+ padding: var(--size-1) var(--size-3);
44
+ white-space: nowrap;
45
+ text-align: left;
46
+ }
47
+ }
48
+ }
49
+
50
+ .relative {
51
+ position: relative;
52
+ }
53
+
54
+ .sticky {
55
+ position: sticky;
56
+ }
57
+
58
+ .flex-row {
59
+ display: flex;
60
+ flex-direction: row;
61
+ gap: var(--size-1);
62
+ }
63
+
64
+ .flex-col {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: var(--size-1);
68
+ }
69
+
70
+ .pagy {
71
+ display: flex;
72
+ font-size: 0.875rem;
73
+ line-height: 1.25rem;
74
+ font-weight: 500;
75
+ color: var(--text-2);
76
+ margin: var(--size-1) 0;
77
+ }
78
+ .pagy > :not([hidden]) ~ :not([hidden]) {
79
+ --space-reverse: 0;
80
+ margin-right: calc(0.25rem * var(--space-reverse));
81
+ margin-left: calc(0.25rem * calc(1 - var(--space-reverse)));
82
+ }
83
+ .pagy a:not(.gap) {
84
+ display: block;
85
+ text-decoration: none;
86
+ border-radius: 0.25rem;
87
+ background: var(--surface-4);
88
+ padding: 0.25rem 0.75rem;
89
+ color: inherit;
90
+ }
91
+ .pagy a:not(.gap):hover {
92
+ background-color: var(--surface-3);
93
+ }
94
+ .pagy a:not(.gap):not([href]) {
95
+ /* disabled links */
96
+ cursor: default;
97
+ background-color: var(--surface-2);
98
+ color: var(--text-2);
99
+ }
100
+ .pagy a:not(.gap).current {
101
+ background: var(--surface-3);
102
+ color: var(--text-2);
103
+ }
104
+ .pagy label {
105
+ white-space: nowrap;
106
+ display: inline-block;
107
+ border-radius: 0.5rem;
108
+ background: var(--surface-2);
109
+ padding: 0.125rem 0.75rem;
110
+ }
111
+ .pagy label input {
112
+ line-height: 1.5rem;
113
+ border-radius: 0.375rem;
114
+ border-style: none;
115
+ background: var(--surface-2);
116
+ }
117
+ }
@@ -0,0 +1,24 @@
1
+ <%= form_with(model: resource) do |form| %>
2
+ <% if resource.errors.any? %>
3
+ <div style="color: red">
4
+ <h2>
5
+ <%= pluralize(resource.errors.count, "error") %> prohibited this <%= resource_name %> from being saved:
6
+ </h2>
7
+ <ul>
8
+ <% resource.errors.each do |error| %>
9
+ <li><%= error.full_message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+ <% resource_attributes.each do |attr| %>
15
+ <div>
16
+ <%= form.label attr, style: "display: block" %>
17
+ <%= form.text_field attr %>
18
+ </div>
19
+ <% end %>
20
+
21
+ <div>
22
+ <%= form.submit %>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <div class="search-form-container">
2
+ <%= form_tag nil, method: :get do %>
3
+ <%= hidden_field_tag :items, params[:items] %>
4
+ <div class="flex-row">
5
+ <%= search_field_tag :q, params[:q] %>
6
+ <%= submit_tag "Search" %>
7
+ </div>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1,9 @@
1
+ <h1>Editing <%= resource_name %></h1>
2
+
3
+ <%= render "form", resource: @resource %>
4
+ <br>
5
+
6
+ <div>
7
+ <%= link_to "Show this #{ resource_name}", @resource %>
8
+ <%= link_to "Back to #{ resource_name.pluralize }", resource_name.underscore.pluralize.to_sym %>
9
+ </div>
@@ -0,0 +1,49 @@
1
+ <div class="gridy">
2
+ <%= turbo_frame_tag resource_name.underscore.pluralize, "data-turbo-action" => :advance do %>
3
+ <% if searchable? %>
4
+ <%= render "search_form" %>
5
+ <% end %>
6
+ <div class="table-container">
7
+ <div class="relative">
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <% resource_attributes.each do |attr| %>
12
+ <th>
13
+ <%= gridy_table_header(attr, sortable: sortable?) %>
14
+ </th>
15
+ <% end %>
16
+ <th></th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @records.each do |record| %>
21
+ <tr>
22
+ <% resource_attributes.each_with_index do |attr, idx| %>
23
+ <td>
24
+ <% if idx.zero? %>
25
+ <%= link_to record.send(attr), record, "data-turbo" => false %>
26
+ <% else %>
27
+ <%= record.send(attr) %>
28
+ <% end %>
29
+ </td>
30
+ <% end %>
31
+ <td>
32
+ <span><%= link_to "Show", record, "data-turbo" => false %></span>
33
+ <span><%= link_to "Edit", [:edit, record], "data-turbo" => false %></span>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ <tfoot>
39
+ <tr>
40
+ <td colspan="<%= resource_attributes.count + 1 %>">
41
+ <%== pagy_nav(@pagy) %>
42
+ </td>
43
+ </tr>
44
+ </tfoot>
45
+ </table>
46
+ </div>
47
+ </div>
48
+ <% end %>
49
+ </div>
@@ -0,0 +1,8 @@
1
+ <h1>New <%= resource_name %></h1>
2
+
3
+ <%= render "form", resource: @resource %>
4
+ <br>
5
+
6
+ <div>
7
+ <%= link_to "Back to #{ resource_name.pluralize }", resource_name.underscore.pluralize.to_sym %>
8
+ </div>
@@ -0,0 +1,16 @@
1
+ <p style="color: green"><%= notice %></p>
2
+
3
+ <div id="<%= dom_id(@resource) %>">
4
+ <% resource_attributes.each do |attr| %>
5
+ <p>
6
+ <strong><%= attr.to_s.humanize %>:</strong>
7
+ <%= @resource.send(attr) %>
8
+ </p>
9
+ <% end %>
10
+ </div>
11
+ <div>
12
+ <%= link_to "Edit this #{ resource_name}", [:edit, @resource] %>
13
+ <%= link_to "Back to #{ resource_name.pluralize }", resource_name.underscore.pluralize.to_sym %>
14
+
15
+ <%= button_to "Destroy this #{resource_name}", @resource, method: :delete %>
16
+ </div>
@@ -0,0 +1,73 @@
1
+ require "pagy"
2
+
3
+ module Gridy
4
+ module Controller
5
+ module Actions
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Gridy::Controller
10
+ before_action :set_resource, only: %i[ show edit update destroy ]
11
+ end
12
+
13
+ def index
14
+ gridy_collection(collection, params)
15
+ render "index"
16
+ end
17
+
18
+ def show
19
+ end
20
+
21
+ def new
22
+ self.resource_instance = resource_class.new
23
+ end
24
+
25
+ def edit
26
+ end
27
+
28
+ def create
29
+ self.resource_instance = resource_class.new(resource_params)
30
+
31
+ if resource_instance.save
32
+ redirect_to resource_instance, notice: "#{resource_name.titleize} was successfully created."
33
+ else
34
+ render :new, status: :unprocessable_entity
35
+ end
36
+ end
37
+
38
+ def update
39
+ if resource_instance.update(resource_params)
40
+ redirect_to resource_instance, notice: "#{resource_name.titleize} was successfully updated."
41
+ else
42
+ render :edit, status: :unprocessable_entity
43
+ end
44
+ end
45
+
46
+ def destroy
47
+ resource_instance.destroy!
48
+ redirect_to resource_index_url, notice: "#{resource_name.titleize} was successfully destroyed."
49
+ end
50
+
51
+ private
52
+
53
+ def resource_class
54
+ self.class.resource
55
+ end
56
+
57
+ def collection
58
+ resource_class.all
59
+ end
60
+
61
+ def set_resource
62
+ self.resource_instance = resource_class.find(params[:id])
63
+ end
64
+
65
+ def resource_params
66
+ resource_named_params = "#{resource_name}_params"
67
+ return send(resource_named_params) if respond_to?(resource_named_params)
68
+
69
+ params.require(resource_name.underscore.to_sym).permit(*resource_attributes)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,94 @@
1
+ require "pagy"
2
+ require "ransack"
3
+
4
+ module Gridy
5
+ module Controller
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Pagy::Backend
10
+
11
+ helper_method :resource_attributes_types, :resource_attributes, :resource_name, :searchable?, :sortable?
12
+ end
13
+
14
+ class_methods do
15
+ def gridy(options)
16
+ options.symbolize_keys!
17
+ @resource = options[:model] if options[:model].present?
18
+ @resource_attributes = options[:attributes] if options[:attributes].present?
19
+ @searchable = options[:searchable].presence || false
20
+ @sortable = options[:sortable].presence || true
21
+ end
22
+
23
+ def resource
24
+ @resource.presence || controller_name.classify.constantize
25
+ end
26
+
27
+ def resource_attributes_types
28
+ resource.attributes_builder.types
29
+ end
30
+
31
+ def resource_attributes
32
+ @resource_attributes.presence || resource_attributes_types.keys
33
+ end
34
+
35
+ def searchable?
36
+ @searchable
37
+ end
38
+
39
+ def sortable?
40
+ @sortable
41
+ end
42
+ end
43
+
44
+ def gridy_collection(collection, options = {})
45
+ @ransack = gridy_query(collection, options)
46
+ @pagy, @records = pagy(@ransack.result(distinct: true), items: options[:items] || 20)
47
+ instance_variable_set("@#{resource_name.pluralize.underscore}", @records)
48
+ end
49
+
50
+ def gridy_query(collection, options = {})
51
+ query = {}
52
+
53
+ query[self.class.resource.searchable_key] = options[:q] if searchable?
54
+ query[:s] = options[:sort] if sortable?
55
+
56
+ collection.ransack(query)
57
+ end
58
+
59
+ private
60
+
61
+ def searchable?
62
+ self.class.searchable?
63
+ end
64
+
65
+ def sortable?
66
+ self.class.sortable?
67
+ end
68
+
69
+ def resource_attributes_types
70
+ self.class.resource_attributes_types
71
+ end
72
+
73
+ def resource_attributes
74
+ self.class.resource_attributes
75
+ end
76
+
77
+ def resource_name
78
+ self.class.resource.name
79
+ end
80
+
81
+ def resource_index_url
82
+ send "#{resource_name.underscore.pluralize}_url"
83
+ end
84
+
85
+ def resource_instance
86
+ instance_variable_get("@#{resource_name.underscore}")
87
+ end
88
+
89
+ def resource_instance=(value)
90
+ @resource = value
91
+ instance_variable_set("@#{resource_name.underscore}", @resource)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,33 @@
1
+ require "ransack"
2
+
3
+ module Gridy
4
+ module Model
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def searchable_attributes
9
+ column_names.select { |field| attributes_builder.types[field.to_s].type.eql?(:string) }
10
+ end
11
+
12
+ def searchable_key
13
+ searchable_attributes.join("_or_") + "_cont"
14
+ end
15
+
16
+ def sortable_attributes
17
+ column_names
18
+ end
19
+
20
+ def ransackable_attributes(auth_object = nil)
21
+ searchable_attributes
22
+ end
23
+
24
+ def ransortable_attributes(auth_object = nil)
25
+ sortable_attributes
26
+ end
27
+
28
+ def ransackable_associations(auth_object = nil)
29
+ []
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module Gridy
2
+ class Railtie < ::Rails::Engine
3
+ PRECOMPILE_ASSETS = [
4
+ "gridy.css",
5
+ "gridy/open-props.css",
6
+ "gridy/open-props/normalize.css",
7
+ "gridy/open-props/buttons.css"
8
+ ].freeze
9
+
10
+ initializer "gridy.assets" do
11
+ if Rails.application.config.respond_to?(:assets)
12
+ Rails.application.config.assets.precompile += PRECOMPILE_ASSETS
13
+ end
14
+ end
15
+
16
+ initializer "gridy.view_helpers" do
17
+ ActiveSupport.on_load(:action_view) do
18
+ require "gridy/view_helpers"
19
+ include Gridy::ViewHelpers
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Gridy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,36 @@
1
+ module Gridy
2
+ module ViewHelpers
3
+ include Turbo::FramesHelper
4
+ include Pagy::Frontend
5
+
6
+ def gridy_search_url
7
+ uri = URI(request.url)
8
+ params = Rack::Utils.parse_query(uri.query)
9
+ params.delete("page")
10
+ uri.query = params.to_query
11
+ uri.to_s
12
+ end
13
+
14
+ def gridy_table_header(field, title = nil, sortable: false)
15
+ unless sortable
16
+ return content_tag(:th, title || field.to_s.titleize)
17
+ end
18
+
19
+ uri = URI(request.url)
20
+ params = Rack::Utils.parse_query(uri.query)
21
+
22
+ if params["sort"].to_s.start_with?(field.to_s)
23
+ icon = params["sort"].to_s.end_with?("asc") ? "▲" : "▼"
24
+ else
25
+ icon = ""
26
+ end
27
+
28
+ params["sort"] = "#{field} #{params["sort"] == "#{field} asc" ? "desc" : "asc"}"
29
+ uri.query = params.to_query
30
+
31
+ link_to(uri.to_s, class: "") do
32
+ content_tag(:span, "#{title || field.to_s.titleize} #{icon}".html_safe)
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/gridy.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "gridy/version"
2
+ require "gridy/railtie"
3
+ require "zeitwerk"
4
+ require "turbo-rails"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.setup
8
+
9
+ module Gridy
10
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :gridy do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gridy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Melvin Sembrano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pagy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '8.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '8.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.1.3.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.1.3.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: ransack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: turbo-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: zeitwerk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '2.6'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '2.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: debug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '1.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '1.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rails-omakase
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
111
+ description: Ruby on Rails grid made easy
112
+ email:
113
+ - melv@hey.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - MIT-LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - app/assets/stylesheets/gridy.css
123
+ - app/assets/stylesheets/gridy/open-props.css
124
+ - app/assets/stylesheets/gridy/open-props/buttons.css
125
+ - app/assets/stylesheets/gridy/open-props/normalize.css
126
+ - app/views/application/_form.html.erb
127
+ - app/views/application/_search_form.html.erb
128
+ - app/views/application/edit.html.erb
129
+ - app/views/application/index.html.erb
130
+ - app/views/application/new.html.erb
131
+ - app/views/application/show.html.erb
132
+ - lib/gridy.rb
133
+ - lib/gridy/controller.rb
134
+ - lib/gridy/controller/actions.rb
135
+ - lib/gridy/model.rb
136
+ - lib/gridy/railtie.rb
137
+ - lib/gridy/version.rb
138
+ - lib/gridy/view_helpers.rb
139
+ - lib/tasks/gridy_tasks.rake
140
+ homepage: http://github.com/melvinsembrano/gridy
141
+ licenses:
142
+ - MIT
143
+ metadata:
144
+ homepage_uri: http://github.com/melvinsembrano/gridy
145
+ source_code_uri: https://github.com/melvinsembrano/gridy
146
+ changelog_uri: https://github.com/melvinsembrano/gridy/blob/master/CHANGELOG.md
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 3.4.19
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Ruby on Rails grid helper
166
+ test_files: []