gridy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []