component_party 0.8.0

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