component_party 0.8.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 73fbc5bb12f3bbbd726ca0314afeac72fddf416b0adf9c0a324c74722675349d
4
+ data.tar.gz: 251c72e7dac7ad1328ba42742fa3439cfeb69b009efd1937ef981bcdf7a90aa1
5
+ SHA512:
6
+ metadata.gz: 46b22b5d9a78f1927117e331ddcd90d07806e77f8118fe46683c281767485805ac30731aa91d8f26a0aca6f37f81255ff3b2af433bc533582c1c6c5afc92abd3
7
+ data.tar.gz: eac0e6c9ca872d6ba997db86c52a869186bb9e4b528746b65bf35b2797a7e1932cad5a0228c6410c25f9f172bee58ce11cd5710abb5371fa7546ede646385a35
data/.codeclimate ADDED
@@ -0,0 +1,23 @@
1
+ engines:
2
+ rubocop:
3
+ enabled: true
4
+ #checks:
5
+ # Rubocop/Metrics/ClassLength:
6
+ # enabled: false
7
+ brakeman:
8
+ enabled: false
9
+ eslint:
10
+ enabled: false
11
+ csslint:
12
+ enabled: false
13
+ duplication:
14
+ enabled: true
15
+ config:
16
+ languages:
17
+ - ruby:
18
+ - javascript:
19
+ ratings:
20
+ paths:
21
+ - lib/**
22
+ exclude_paths:
23
+ - spec/**/*
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/support/rails_app/log/*
10
+ /tmp/
11
+ .DS_Store
12
+ .byebug_history
13
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ Documentation:
4
+ Enabled: false
5
+
6
+ Style/ClassAndModuleChildren:
7
+ Enabled: false
8
+
9
+ Style/FrozenStringLiteralComment:
10
+ Enabled: false
11
+
12
+ Metrics/LineLength:
13
+ Max: 150
14
+ IgnoredPatterns: ['\A#']
15
+
16
+ Metrics/MethodLength:
17
+ Max: 20
18
+
19
+ AllCops:
20
+ Exclude:
21
+ - component_party.gemspec
22
+ - 'exe/**/*'
23
+ - 'bin/**/*'
24
+ - 'spec/**/*'
data/.rubocop_todo.yml ADDED
File without changes
data/.travis.yml ADDED
@@ -0,0 +1,36 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=2373c9fa7226b98ccf8bef72f133bc37b4cef57085c027e144a3b2fc71ce7558
4
+ matrix:
5
+ - "RAILS_VERSION=4.2.0"
6
+ - "RAILS_VERSION=5.0.0"
7
+ - "RAILS_VERSION=5.1.0"
8
+ - "RAILS_VERSION=5.2.0"
9
+
10
+ language: ruby
11
+
12
+ cache:
13
+ bundler: true
14
+
15
+ rvm:
16
+ - "2.4.1"
17
+
18
+ bundler_args: "--binstubs --standalone --without documentation --path ../bundle"
19
+
20
+ before_install:
21
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
22
+ - chmod +x ./cc-test-reporter
23
+
24
+ before_script:
25
+ - ./cc-test-reporter before-build
26
+
27
+ script:
28
+ - bundle exec rubocop -c .rubocop.yml
29
+ - bundle exec rspec
30
+
31
+ notifications:
32
+ email: false
33
+
34
+ after_script:
35
+ - if [[ "$TRAVIS_TEST_RESULT" == 0 ]]; then ./cc-test-reporter format-coverage -t simplecov -o ./coverage/codeclimate.$CI_NODE_INDEX.json ./coverage/spec/.resultset.json; fi
36
+ - if [[ "$TRAVIS_TEST_RESULT" == 0 ]]; then ./cc-test-reporter sum-coverage --output - --parts $CI_NODE_TOTAL coverage/codeclimate.*.json | ./cc-test-reporter upload-coverage --input -; fi
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --no-private
2
+ --markup markdown
3
+ --default-return void
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'yard', '~> 0.9.9', require: false
4
+
5
+ rails_version = ENV['RAILS_VERSION'] || 'default'
6
+
7
+ rails = case rails_version
8
+ when 'master'
9
+ { github: 'rails/rails' }
10
+ when 'default'
11
+ '~> 4.2.0'
12
+ else
13
+ "~> #{rails_version}"
14
+ end
15
+
16
+ gem 'rails', rails
17
+ # Specify your gem's dependencies in rabbitmq-spec.gemspec
18
+ gemspec
data/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # Welcome to component_party gem
2
+
3
+ [![Travis](https://travis-ci.com/viniciusoyama/component_party.svg?branch=master)](https://travis-ci.com/viniciusoyama/component_party)
4
+ [![Code Climate](https://codeclimate.com/github/viniciusoyama/component_party/badges/gpa.svg)](https://codeclimate.com/github/viniciusoyama/component_party)
5
+ [![Test Coverage](https://codeclimate.com/github/viniciusoyama/component_party/badges/coverage.svg)](https://codeclimate.com/github/viniciusoyama/component_party)
6
+
7
+ Frontend components for Ruby on Rails: group your view logic, html and css files in components to be rendered from views or directly from controllers.
8
+
9
+ # Table of contents
10
+
11
+ <!-- TOC depthFrom:1 depthTo:1 withLinks:1 updateOnSave:0 orderedList:0 -->
12
+
13
+ - [Quick overview](#quick-overview)
14
+ - [Importing components](#importing-components)
15
+ - [View Models: pass data to your components](#view-models-pass-data-to-your-components)
16
+ - [Using helpers inside your components](#using-helpers-inside-your-components)
17
+ - [Accessing params and other controller's methods](#accessing-params-and-other-controllers-methods)
18
+ - [Rendering a component from a controller's action](#rendering-a-component-from-a-controllers-action)
19
+ - [Style namespacing: css scoped by component](#style-namespacing-css-scoped-by-component)
20
+ - [Configuration](#configuration)
21
+
22
+ <!-- /TOC -->
23
+
24
+ # Quick overview
25
+
26
+ ## Installation
27
+ Add to your gemfile: `gem 'component_party'`
28
+
29
+ ## Using the gem
30
+
31
+ 1) Move things to app/components folder and organize your frontend
32
+
33
+
34
+ ```
35
+ app
36
+ ├── components
37
+ │ └── sidebar
38
+ │ └── template.erb
39
+ │ └── style.sass
40
+ │ └── header
41
+ │ └── template.erb
42
+ │ └── style.sass
43
+ │ └── pages
44
+ │ └── users
45
+ │ └── index
46
+ │ └── template.erb
47
+ │ └── style.sass
48
+ │ └── filter
49
+ │ └── template.erb
50
+ │ └── view_model.rb
51
+ │ └── style.sass
52
+ │ └── list
53
+ │ └── template.erb
54
+ │ └── style.sass
55
+ ```
56
+
57
+
58
+ 2) Code your templates
59
+
60
+ **app/components/pages/users/index/template.erb**
61
+
62
+ ```html
63
+ <%
64
+ import_component 'Filter', path: './filter'
65
+ import_component 'List', path: './list'
66
+ %>
67
+
68
+ <%= Filter() %>
69
+
70
+ <%= List(users: users) %>
71
+ ```
72
+
73
+ **app/components/pages/users/list/template.erb**
74
+
75
+ ```html
76
+ <table>
77
+ <tbody>
78
+ <% vm.users.each do |user| %>
79
+ <tr>
80
+ <td><%= user.name %></td>
81
+ </tr>
82
+ <% end %>
83
+ </tbody>
84
+ </table>
85
+ ```
86
+
87
+ 3) Render a component from your controller
88
+
89
+ ```ruby
90
+
91
+ class UsersController < ApplicationController
92
+
93
+ def index
94
+ render component: true, view_model_data: { users: User.all }
95
+ end
96
+
97
+ end
98
+ ```
99
+
100
+ 4) Customize and namespace your css for a given component
101
+
102
+ **app/components/pages/users/list/style.sass**
103
+
104
+ ```sass
105
+ [data-component-path=pages-users-list] table tr
106
+ background: blue
107
+ ```
108
+
109
+ 5) Be astonished by what you've accomplished
110
+
111
+ ![I'm so cool](rainbow_meme_v1.jpg?raw=true "Title")
112
+ ![Party Hard](partyhard.gif?raw=true "Title")
113
+
114
+ # Importing components
115
+
116
+ ```
117
+ app
118
+ ├── components
119
+ │ └── application_layout
120
+ │ └── header
121
+ │ └── template.html.erb
122
+ │ └── user
123
+ │ └── panel
124
+ │ └── sidebar
125
+ │ └── template.html.erb
126
+ │ └── template.html.erb
127
+ ```
128
+
129
+ You can import a component inside a layout, view or a component's template.
130
+
131
+ ## Absolute importing
132
+
133
+ Just use the full component path.
134
+
135
+ **app/views/layouts/application.html.erb**
136
+
137
+ ```html
138
+
139
+ <%
140
+ import_component 'Header', path: 'application_layout/header'
141
+ %>
142
+
143
+ <%= Header(my_user: current_user) %>
144
+ ```
145
+
146
+ ## Relative importing
147
+
148
+ Use "./" before component's path.
149
+
150
+ The next example will look for a `sidebar` component inside the app/components/user/panel folder.
151
+
152
+ **app/components/user/panel/sidebar.html.erb**
153
+
154
+ ```html
155
+
156
+ <%
157
+ import_component 'Sidebar', path: './sidebar'
158
+ %>
159
+
160
+ <%= Sidebar() %>
161
+ ```
162
+
163
+ # View Models: pass data to your components
164
+
165
+ While rendering a component, you can pass data in a hash format. The data will automatically be exposed to your template though a `vm` variable.
166
+
167
+ ```
168
+ app
169
+ ├── components
170
+ │ └── header
171
+ │ └── template.html.erb
172
+ ```
173
+
174
+ Rendering the component and passing data:
175
+
176
+ ```html
177
+
178
+ <%
179
+ import_component 'Header', path: 'header'
180
+ %>
181
+
182
+ <%= Header(my_user: current_user) %>
183
+ ```
184
+
185
+ You have access to the my_user attribute in your component's template:
186
+
187
+ The component's template code:
188
+
189
+ ```html
190
+ <p>Hello <%= vm.my_user.name %></p>
191
+ ```
192
+
193
+ ## How it works?
194
+
195
+ The `vm` variable is an instance of a ViewModel.
196
+
197
+ By default, we instantiate our `ComponentParty::ViewModel`. This class takes all the constructor arguments (it must be a hash/named args) and creates a getter for each one of them. Example:
198
+
199
+ ```ruby
200
+ vm = ComponentParty::ViewModel.new(name: 'John', age: 12)
201
+ vm.name # John
202
+ vm.age # 12
203
+ ```
204
+
205
+ When a view model is instantiated we pass the all arguments that you provided while calling your component in the template.
206
+
207
+ ## Create your own ViewModel and handle complex view logic
208
+
209
+ Suppose that we want to use a custom view model (inside our header component's folder).
210
+
211
+ **app/components/header/view_model.rb**
212
+
213
+ ```ruby
214
+ class Header::ViewModel < ComponentParty::ViewModel
215
+ def random_greeting
216
+ hi_text = ['Hi', 'Yo'].sample
217
+ "#{hi_text}, #{user.name}"
218
+ end
219
+ end
220
+ ```
221
+
222
+ While importing our Header component we can pass a `custom_view_model` option to the import directive.
223
+
224
+ ```html
225
+ <%
226
+ import_component 'Header', path: 'header', custom_view_model: true
227
+ %>
228
+
229
+ <%= Header(name: 'John') %>
230
+ ```
231
+
232
+ By default we will use our own view model. But if you pass the `custom_view_model` options as `true` the rendering process will look for a class following all the Rails naming conventions.
233
+
234
+ Also, you can use any class as a view model. Instead of `true` just use the class itself:
235
+
236
+ ```html
237
+ <%
238
+ import_component 'Header', path: 'header', custom_view_model: MyCustomClass
239
+ %>
240
+
241
+ <%= Header(name: 'John') %>
242
+ ```
243
+
244
+ *PS:* You need to pass the class and not an instance of it.
245
+
246
+ Note that a view model *must* inherit from ComponentParty::ViewModel in order to be compliant to the internal API that a view model must have. Also, it is not expected that you override the `initialize` method in your custom view model.
247
+
248
+ **Is it possible to (by default) automatically search for a custom view model and, if not found, just use the default one instead?**
249
+
250
+ Yes, it would be possible to avoid the need of you passing a `custom_view_model` option but we don't like this approach for 2 main reasons:
251
+
252
+ 1) This feature would make the rendering process much slower.
253
+
254
+ 2) We think it's better to do things more explicitly for who is reading your code.
255
+
256
+ # Using helpers inside your components
257
+
258
+ The component's template is compiled using a normal rails view context so you have access to all helpers/params/routes/etc:
259
+
260
+ ```
261
+ <div class="child">
262
+ <div class="date">
263
+ <%= l(Date.new(2019, 01, 03)) %>
264
+ </div>
265
+
266
+ <div class="routes">
267
+ <%= users_path %>
268
+ </div>
269
+
270
+ <div class="translation">
271
+ <%= t('hello')%>
272
+ </div>
273
+ </div>
274
+ ```
275
+
276
+ If you want to to access it inside a view model, just use the `view` method.
277
+
278
+ ```ruby
279
+ class Header::ViewModel < ComponentParty::ViewModel
280
+ def formated_date
281
+ view.l(Date.today)
282
+ end
283
+ end
284
+ ```
285
+
286
+ **app/components/header/template.html.erb**
287
+ ```html
288
+ <header>
289
+ Today is <%= vm.formated_date %>
290
+ </header>
291
+ ```
292
+
293
+
294
+ # Accessing params and other controller's methods
295
+
296
+
297
+ ```ruby
298
+ class Header::ViewModel < ComponentParty::ViewModel
299
+
300
+ def formated_page
301
+ "Current page: #{view.params[:page]}"
302
+ end
303
+
304
+ def formated_search
305
+ "Searching for: #{view.params[:search]}"
306
+ end
307
+
308
+ def hello
309
+ "Hi #{view.current_user.name}"
310
+ end
311
+
312
+ end
313
+ ```
314
+
315
+ **template.erb**
316
+
317
+ ```html
318
+ <%= vm.hello %>
319
+
320
+ <%= vm.formated_search %>
321
+
322
+ <%= vm.formated_page %>
323
+
324
+ <%= params[:search] %>
325
+ ```
326
+
327
+ # Rendering a component from a controller's action
328
+
329
+ When you are in an action you can render a component instead of a rails view using the following syntax:
330
+
331
+ ```ruby
332
+
333
+ class ClientsController < ApplicationController
334
+
335
+ def index
336
+ render(component: 'my/component/path', view_model_data: { new_arg: 2, more_arg: 'text'})
337
+ end
338
+
339
+ end
340
+
341
+ ```
342
+
343
+ If you want to render the default component for an given action just do:
344
+
345
+
346
+ ```ruby
347
+ class ClientsController < ApplicationController
348
+
349
+ def index
350
+ render component: true, view_model_data: { clients: Client.all }
351
+ end
352
+
353
+ end
354
+ ```
355
+
356
+ This will search for a component with a path of `app/components/pages/clients/index`.
357
+
358
+ Note that we will add **pages** before the default controller+action path.
359
+
360
+ **Can I make the gem render a component instead of a view by default?**
361
+
362
+ We though about doing this but - even if the default behavior was to render a component instead of a view - you would have to pass the view model data in the action using some kind of trick like:
363
+
364
+ ```
365
+ def index
366
+ set_view_model_attribute(:users, User.all)
367
+ end
368
+ ```
369
+
370
+ Using the controller's instance variables just like views doesn't make sense in a ViewModel implementation.
371
+
372
+ Also, as we want to make things more explicitly (in case of another dev that doesn't know about this gem enters the project), it's better to always have to write the command.
373
+
374
+ ```
375
+ render component: true
376
+ ```
377
+
378
+ # Style namespacing: css scoped by component
379
+
380
+ Your template **must** have only one root node in order to your component be namespaced.
381
+
382
+ Each rendered component will have its first HTML node changed with a dynamic data attribute storing the component path. This means that you can create custom css for each component. Example:
383
+
384
+ ```
385
+ app
386
+ ├── components
387
+ │ └── admin_layout
388
+ │ └── header
389
+ │ └── template.html.erb
390
+ │ └── style.css
391
+ ```
392
+
393
+ ```html
394
+ <section>
395
+ <h1>Hello <%= vm.user_name%></h1>
396
+ </section>
397
+ ```
398
+
399
+ When we render the header it will generate a HTML like this
400
+
401
+ ```html
402
+ <section data-component-path='admin_layout-header'>
403
+ <h1>Hello <%= vm.user_name%></h1>
404
+ </section>
405
+ ```
406
+
407
+ Then you can customize the component with the following css:
408
+
409
+ ```css
410
+ [data-component-path=admin_layout-header] {
411
+ background: red;
412
+ }
413
+ ```
414
+
415
+ ## How to import the css files?
416
+
417
+ You can split your css in each component folder. It doesn't matter the name or the number of css/sass/less files that you have in each folder... Just don't forget to namespace it!
418
+
419
+ Also, in your application.css file you should require all the css from the app/components folder with a relative `require_tree`. Like this:
420
+
421
+ **application.sass**
422
+
423
+ ```sass
424
+ //*=require_tree ../../../components
425
+ @import "fullcalendar.min"
426
+ @import "bootstrap"
427
+ @import "datepicker"
428
+
429
+ // ...
430
+ ```
431
+
432
+ # Configuration
433
+
434
+ You can customize our gem by creating an initializer on your rails app.
435
+
436
+ **config/initializers/component_party.rb**
437
+
438
+ ```ruby
439
+
440
+ ComponentParty.configure do |config|
441
+ # Folder path to look for components
442
+ config.components_path = 'app/components'
443
+
444
+ # Name for the html/erb/slim/etc template file inside the component folder
445
+ config.template_file_name = 'template'
446
+
447
+ # Name for the view model file inside the component folder
448
+ config.view_model_file_name = 'view_model'
449
+
450
+ # Folder path inside the components folder to look for pages when
451
+ # rendering the default component for a controller#action
452
+ config.component_folder_for_actions = 'pages'
453
+ end
454
+
455
+ ```
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'component_party'
3
+ s.version = '0.8.0'
4
+ s.date = '2019-03-28'
5
+ s.summary = 'Stop using views: frontend components architecture for Ruby on Rails.'
6
+ s.description = 'Frontend components for Ruby on Rails: group your view logic, html and css files in components to be rendered from views or directly from controllers.'
7
+ s.authors = ['Vinícius Oyama']
8
+ s.email = 'contact@viniciusoyama.com'
9
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
10
+ s.homepage =
11
+ 'https://github.com/viniciusoyama/component_party'
12
+ s.license = 'MIT'
13
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
14
+
15
+ s.add_dependency 'rails', '>= 4.2.0', '< 6'
16
+
17
+ # Quality Control
18
+ s.add_development_dependency 'rspec'
19
+ s.add_development_dependency 'rubocop'
20
+ s.add_development_dependency 'simplecov'
21
+
22
+ # Debugging
23
+ s.add_development_dependency 'awesome_print'
24
+ s.add_development_dependency 'pry-byebug'
25
+
26
+ # for testing a gem with a rails app (controller specs)
27
+ # https://codingdaily.wordpress.com/2011/01/14/test-a-gem-with-the-rails-3-stack/
28
+ s.add_development_dependency 'bundler'
29
+ s.add_development_dependency 'capybara'
30
+ s.add_development_dependency 'rspec-rails'
31
+ s.add_development_dependency 'sqlite3'
32
+ end
@@ -0,0 +1,26 @@
1
+ # Decorates the template renderer chosen to render
2
+ # the current view/template/file.
3
+ # It overrides the render method in order to wrap the rendered template
4
+ # in a tag with data attributes
5
+ class ComponentParty::ActionView::ComponentRenderer::TagWrapperDecorator < SimpleDelegator
6
+ attr_reader :component_path
7
+
8
+ def initialize(target, component_path)
9
+ @component_path = component_path
10
+ super(target)
11
+ end
12
+
13
+ def render(context, options = {}, &block)
14
+ rendered = __getobj__.render(context, options, &block)
15
+ rendered = apply_html_namespacing(rendered.to_s)
16
+ ActionView::OutputBuffer.new(rendered)
17
+ end
18
+
19
+ def apply_html_namespacing(raw_html)
20
+ component_id = component_path.to_s.gsub(%r{^/}, '').tr('/', '-')
21
+ html_tag_end_regex = %r{(/?)>}
22
+ raw_html.sub(html_tag_end_regex) do
23
+ " data-component-path='#{component_id}' #{Regexp.last_match(1)}>"
24
+ end.html_safe
25
+ end
26
+ end
@@ -0,0 +1,54 @@
1
+ module ComponentParty
2
+ # Renders a given component
3
+ module ActionView
4
+ class ComponentRenderer < ::ActionView::TemplateRenderer
5
+ attr_reader :lookup_context
6
+ attr_reader :component_path
7
+
8
+ def initialize(lookup_context, component_path)
9
+ @component_path = component_path
10
+ super(lookup_context)
11
+ end
12
+
13
+ def render(context, options)
14
+ options[:file] = template_path_from_component_path(options[:component])
15
+ options[:locals] = { vm: create_view_model(context, options) }
16
+ super(context, options)
17
+ end
18
+
19
+ def render_template(template, layout_name = nil, locals = nil) #:nodoc:
20
+ super(decorate_template(template), layout_name, locals)
21
+ end
22
+
23
+ def decorate_template(template)
24
+ ComponentParty::ActionView::ComponentRenderer::TagWrapperDecorator.new(template, component_path)
25
+ end
26
+
27
+ def create_view_model(context, options)
28
+ view_model_data = options[:view_model_data] || {}
29
+ view_model_data[:view] = context
30
+
31
+ vm_class = find_vm_class(options)
32
+
33
+ vm_class.new(view_model_data)
34
+ end
35
+
36
+ def find_vm_class(options)
37
+ if options[:custom_view_model]
38
+ if options[:custom_view_model] == true
39
+ vm_file_path = Pathname.new(options[:component]).join(ComponentParty.configuration.view_model_file_name).to_s
40
+ ActiveSupport::Inflector.camelize(vm_file_path).constantize
41
+ else
42
+ options[:custom_view_model]
43
+ end
44
+ else
45
+ ComponentParty::ViewModel
46
+ end
47
+ end
48
+
49
+ def template_path_from_component_path(component_path, template_file_name: ComponentParty.configuration.template_file_name)
50
+ Pathname.new(component_path).join(template_file_name).to_s
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ module ComponentParty #:nodoc:
2
+ # Renders a given component
3
+ module ActionView
4
+ module Renderer
5
+ def render(context, options)
6
+ if options.key?(:component)
7
+ normalize_data_for_component_rendering!(context, options)
8
+ ComponentParty::ActionView::ComponentRenderer.new(lookup_context, options[:component]).render(context, options)
9
+ else
10
+ super(context, options)
11
+ end
12
+ end
13
+
14
+ def normalize_data_for_component_rendering!(context, options)
15
+ normalize_component_path!(context, options)
16
+ context.instance_variable_set('@current_component_path', options[:component])
17
+ end
18
+
19
+ # An example of options argumento passed by Rails are
20
+ # {
21
+ # :prefixes=>["devise/sessions", "devise", "application"],
22
+ # :template=>"new",
23
+ # :layout=> a Proc
24
+ # }
25
+ # rubocop:disable Metrics/LineLength
26
+ def normalize_component_path!(_context, options)
27
+ if options[:component] == true
28
+ options[:component] = Pathname.new(ComponentParty.configuration.component_folder_for_actions).join(options[:prefixes].first.to_s, options[:template]).to_s
29
+ else
30
+ options[:component]
31
+ end
32
+ end
33
+ # rubocop:enable all
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ module ComponentParty
2
+ # Exposes component rendering methods to Rails views
3
+
4
+ module ImporterHelper
5
+ # Description of #import_component
6
+ #
7
+ # @param local_component_name [String] Local's component name (in the view scope)
8
+ # @param opts [Hash] default: {} Options.
9
+ # @example
10
+ def import_component(local_component_name, opts = {})
11
+ raise "No path informed when importing component #{local_component_name}" if opts[:path].blank?
12
+
13
+ component_to_render_path = get_full_component_path(opts[:path])
14
+
15
+ define_singleton_method(local_component_name) do |**args|
16
+ render(component: component_to_render_path, view_model_data: args, custom_view_model: (opts[:custom_view_model] || false))
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def get_full_component_path(path)
23
+ if path.starts_with?('./')
24
+ current_component_path = instance_variable_get('@current_component_path')
25
+
26
+ raise "You cannot use a relative component importing outside a component's template." if current_component_path.blank?
27
+
28
+ Pathname.new(current_component_path).join(path).to_s
29
+ else
30
+ path
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ require 'rails/railtie'
2
+
3
+ class ViewComponent
4
+ class Rails < Rails::Railtie
5
+ initializer 'view_component' do
6
+ config.assets.paths << ::Rails.root.join(ComponentParty.configuration.components_path)
7
+
8
+ ActiveSupport.on_load(:action_controller) do
9
+ prepend_view_path(::Rails.root.join(ComponentParty.configuration.components_path)) if respond_to?(:prepend_view_path)
10
+ end
11
+
12
+ ::ActionView::Renderer.send(:prepend, ComponentParty::ActionView::Renderer)
13
+ ::ActionView::Base.send(:include, ComponentParty::ImporterHelper)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module ComponentParty
2
+ class ViewModel
3
+ def initialize(**args)
4
+ generate_methods_from_hash(args)
5
+ end
6
+
7
+ private
8
+
9
+ def generate_methods_from_hash(hash)
10
+ hash.each do |key, val|
11
+ define_singleton_method key.to_sym do
12
+ val
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ require 'component_party/importer_helper'
2
+ require 'component_party/view_model'
3
+ require 'component_party/action_view/renderer'
4
+ require 'component_party/action_view/component_renderer'
5
+ require 'component_party/action_view/component_renderer/tag_wrapper_decorator'
6
+
7
+ module ComponentParty
8
+ # Configuration class for ComponentParty
9
+ # @attr components_path [String] Components folder path (defaults to 'app/components')
10
+ # @attr components_path [String] Component's template file name (defaults to 'template')
11
+ class Configuration
12
+ attr_accessor :components_path
13
+ attr_accessor :template_file_name
14
+ attr_accessor :view_model_file_name
15
+ attr_accessor :component_folder_for_actions
16
+
17
+ def initialize
18
+ @components_path = 'app/components'
19
+ @template_file_name = 'template'
20
+ @view_model_file_name = 'view_model'
21
+ @component_folder_for_actions = 'pages'
22
+ end
23
+ end
24
+
25
+ def self.configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ def self.configure
30
+ yield(configuration)
31
+ end
32
+ end
33
+
34
+ require 'component_party/railtie'
data/partyhard.gif ADDED
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: component_party
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Vinícius Oyama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 4.2.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: simplecov
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: awesome_print
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry-byebug
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: bundler
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: capybara
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: rspec-rails
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: sqlite3
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ description: 'Frontend components for Ruby on Rails: group your view logic, html and
160
+ css files in components to be rendered from views or directly from controllers.'
161
+ email: contact@viniciusoyama.com
162
+ executables: []
163
+ extensions: []
164
+ extra_rdoc_files: []
165
+ files:
166
+ - ".codeclimate"
167
+ - ".gitignore"
168
+ - ".rspec"
169
+ - ".rubocop.yml"
170
+ - ".rubocop_todo.yml"
171
+ - ".travis.yml"
172
+ - ".yardopts"
173
+ - Gemfile
174
+ - README.md
175
+ - component_party.gemspec
176
+ - lib/component_party.rb
177
+ - lib/component_party/action_view/component_renderer.rb
178
+ - lib/component_party/action_view/component_renderer/tag_wrapper_decorator.rb
179
+ - lib/component_party/action_view/renderer.rb
180
+ - lib/component_party/importer_helper.rb
181
+ - lib/component_party/railtie.rb
182
+ - lib/component_party/view_model.rb
183
+ - partyhard.gif
184
+ - rainbow_meme_v1.jpg
185
+ homepage: https://github.com/viniciusoyama/component_party
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubyforge_project:
205
+ rubygems_version: 2.7.8
206
+ signing_key:
207
+ specification_version: 4
208
+ summary: 'Stop using views: frontend components architecture for Ruby on Rails.'
209
+ test_files: []