resubject 0.3.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 063e070b83031364701371c5c908f0c096723d8315e05e6f2f8e75274079fcaa
4
- data.tar.gz: 8c7554fbb0cf572f0376b8cc72e9ecf21a3ad2f99ac3b4662a9468e3089256fe
3
+ metadata.gz: debe708e5860a43246db83aaf1d49eba9412cea691a4f0ac1ce213a693cafa8e
4
+ data.tar.gz: a431514574d3e653ef8ac244fdb9485dcefdb221b7923d47abd7152b842b676d
5
5
  SHA512:
6
- metadata.gz: 6f4f4586bac272a00799f005f47b0c4e9c7ca8e9d41742be33ab01c0c137a0c9e2c94412a54c19a42372b12b1953e5a05417dbbe04bf8be704cd5b8a426da0c8
7
- data.tar.gz: 26c712b739607e0c501087f4979d442d4cad3fbcf4cd1e62ffb1ac7ef24c46168a6dc4c05b86b2e6c8c3abd3fc696a3c57ffb2d59fa6db0255b72e58bd510576
6
+ metadata.gz: d66af68100a05fc95fc6e9b473bf8c1d35625a263b1639c942b8cebc5e4f9a6190b51cfc6bf132561a26de643d09f76a4e6f56d0d339d9eff0867441ce77085e
7
+ data.tar.gz: b60e51c42e277154052301e5a0ac7d1818a70227036dfdd562234cc4b54e7de5a92ae9bfe8545ac9b5afcad3290d7c5cdee7ee0f34afc795b0d1609ee1a8b7ca
@@ -0,0 +1,17 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ time: "04:00"
8
+ open-pull-requests-limit: 10
9
+ reviewers:
10
+ - felipeelias
11
+ - package-ecosystem: github-actions
12
+ directory: "/"
13
+ schedule:
14
+ interval: weekly
15
+ time: "04:00"
16
+ reviewers:
17
+ - felipeelias
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [master]
7
+ pull_request:
8
+ branches: [master]
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby: ['3.2', '3.3', '3.4']
17
+ rails: ['7.2', '8.0']
18
+ steps:
19
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
20
+ - uses: ruby/setup-ruby@dffb23f65a78bba8db45d387d5ea1bbd6be3ef18 # v1.293.0
21
+ with:
22
+ ruby-version: ${{ matrix.ruby }}
23
+ bundler-cache: true
24
+ cache-version: ${{ matrix.rails }}
25
+ env:
26
+ RAILS_VERSION: ${{ matrix.rails }}
27
+ - run: bundle exec rake spec
28
+ env:
29
+ RAILS_VERSION: ${{ matrix.rails }}
30
+
31
+ lint:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
35
+ - uses: ruby/setup-ruby@dffb23f65a78bba8db45d387d5ea1bbd6be3ef18 # v1.293.0
36
+ with:
37
+ ruby-version: '3.3'
38
+ bundler-cache: true
39
+ - run: bundle exec rubocop
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Dependabot auto-merge
3
+
4
+ on: pull_request
5
+
6
+ permissions:
7
+ contents: write
8
+ pull-requests: write
9
+
10
+ jobs:
11
+ dependabot:
12
+ runs-on: ubuntu-latest
13
+ if: github.actor == 'dependabot[bot]'
14
+ steps:
15
+ - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0
16
+ id: metadata
17
+ - if: steps.metadata.outputs.update-type != 'version-update:semver-major'
18
+ run: |
19
+ gh pr review --approve "$PR_URL"
20
+ gh pr merge --auto --squash "$PR_URL"
21
+ env:
22
+ PR_URL: ${{ github.event.pull_request.html_url }}
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Release Please
3
+
4
+ on:
5
+ push:
6
+ branches: [master]
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
17
+ with:
18
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
19
+ config-file: release-please-config.json
20
+ manifest-file: .release-please-manifest.json
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: Release
3
+
4
+ on:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ push:
10
+ name: Push gem to RubyGems.org
11
+ runs-on: ubuntu-latest
12
+ environment: release
13
+ permissions:
14
+ id-token: write
15
+ contents: write
16
+ steps:
17
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
18
+ with:
19
+ ref: master
20
+ - uses: ruby/setup-ruby@dffb23f65a78bba8db45d387d5ea1bbd6be3ef18 # v1.293.0
21
+ with:
22
+ ruby-version: ruby
23
+ bundler-cache: true
24
+ - uses: rubygems/release-gem@e9a6361a0b14562539327c2a02373edc56dd3169 # v1.1.4
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.0"
3
+ }
data/.rubocop.yml CHANGED
@@ -1,28 +1,11 @@
1
- Metrics/AbcSize:
2
- Max: 20
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+ NewCops: enable
4
+ SuggestExtensions: false
3
5
 
4
6
  Metrics/BlockLength:
5
7
  Exclude:
6
8
  - 'spec/**/*'
7
- - 'test/**/*'
8
-
9
- Metrics/LineLength:
10
- Max: 103
11
-
12
- Metrics/MethodLength:
13
- Max: 15
14
- Exclude:
15
- - 'spec/**/*'
16
- - 'test/**/*'
17
-
18
- Style/AsciiComments:
19
- Enabled: false
20
9
 
21
10
  Style/Documentation:
22
- Exclude:
23
- - 'spec/**/*'
24
- - 'test/**/*'
25
- - 'lib/resubject/builder.rb'
26
- - 'lib/resubject/naming.rb'
27
- - 'lib/resubject/presenter.rb'
28
- - 'lib/resubject/rails.rb'
11
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.6
data/CHANGELOG.md CHANGED
@@ -1,43 +1,54 @@
1
- # 0.2.2
1
+ # Changelog
2
+
3
+ ## [1.0.0](https://github.com/felipeelias/resubject/compare/resubject-v0.3.0...resubject/v1.0.0) (2026-03-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * drop Rails 7.1 from CI matrix (activesupport 7.1 partial require bug) ([20e7ee5](https://github.com/felipeelias/resubject/commit/20e7ee5828ee69ef03ac32c66ba6823eb0d903d5))
9
+ * resolve namespaced presenters without falling back to top-level ([#7](https://github.com/felipeelias/resubject/issues/7)) ([af9fe51](https://github.com/felipeelias/resubject/commit/af9fe51e997e9b355142b48d0283b942b2962114))
10
+ * treat Struct and Enumerable objects as single presentables ([#5](https://github.com/felipeelias/resubject/issues/5)) ([7767a5d](https://github.com/felipeelias/resubject/commit/7767a5dad81dd74dfd8dd0cf3edcf80de9bfb4cd))
11
+
12
+ ## 0.2.2
2
13
  - rubocop style changes
3
14
 
4
- # 0.2.1
15
+ ## 0.2.1
5
16
  - update yard and nokogiri due to security vulnerabilities (only on dev environment)
6
17
 
7
- # 0.2.0
18
+ ## 0.2.0
8
19
 
9
20
  - enhancements
10
21
  - Introduce RSpec helpers
11
22
 
12
- # 0.1.1
23
+ ## 0.1.1
13
24
 
14
25
  - bugfixes
15
26
  - [#2] presents' implicit presenter class blows up with nil association
16
27
  - enhancements
17
28
  - Calls ActiveSupport.run_load_hooks when gem is loaded
18
29
 
19
- # 0.1.0
30
+ ## 0.1.0
20
31
 
21
32
  - enhancements
22
33
  - Add time_ago helper
23
34
  - Add percentage helper
24
35
  - Add date_format helper
25
36
 
26
- # 0.0.4
37
+ ## 0.0.4
27
38
 
28
39
  - bugfixes
29
40
  - Find the presenter in the current object's namespace
30
41
 
31
- # 0.0.3
42
+ ## 0.0.3
32
43
 
33
44
  - Improve docs
34
45
 
35
- # 0.0.2
46
+ ## 0.0.2
36
47
 
37
48
  - Big refactoring
38
49
  - Add `currency` helper
39
50
  - Add `presents` helper
40
51
 
41
- # 0.0.1
52
+ ## 0.0.1
42
53
 
43
54
  - Initial commit
data/CLAUDE.md ADDED
@@ -0,0 +1,36 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Resubject is a Ruby gem for building presenters (decorators) using Ruby's `SimpleDelegator`. It provides automatic presenter resolution by naming convention, Rails integration (controller/view `present` helper), and ActionView-based template method extensions (currency, percentage, time_ago, date_format).
8
+
9
+ Requires Ruby >= 3.1, ActiveSupport >= 7.2. CI tests against Ruby 3.2-3.4 and Rails 7.2-8.0.
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ bundle exec rake spec # run all specs
15
+ bundle exec rspec spec/resubject/presenter_spec.rb # run a single spec file
16
+ bundle exec rspec spec/resubject/presenter_spec.rb:42 # run a single example by line
17
+ bundle exec rubocop # lint
18
+ bundle exec rubocop -a # lint with auto-fix
19
+ RAILS_VERSION=7.2 bundle exec rake spec # test against a specific Rails version
20
+ ```
21
+
22
+ ## Architecture
23
+
24
+ - `lib/resubject/presenter.rb` - Core class inheriting `SimpleDelegator`. Provides `presents` (attribute delegation), `present` (instance-level wrapping), `.all` (collection wrapping), and private helpers (`t`/`l`/`h`/`r` for template context access).
25
+ - `lib/resubject/builder.rb` - `Builder.present` resolves and wraps objects with presenters. Handles single objects vs collections, supports stacking multiple presenters via `inject`.
26
+ - `lib/resubject/naming.rb` - `Naming.presenter_for` infers presenter class from object (e.g., `Post` -> `PostPresenter`, respects namespaces).
27
+ - `lib/resubject/extensions/template_methods.rb` - Class-level DSL methods (`currency`, `percentage`, `time_ago`, `date_format`) that generate instance methods delegating to ActionView helpers.
28
+ - `lib/resubject/rails/` - Rails Engine that auto-includes `Resubject::Helpers` into `ActionController`, providing the `present` controller/view helper. Also adds `routes` (`r`) helper to presenters.
29
+ - `lib/resubject/rspec.rb` - RSpec config that auto-provides `subject` and `template` for specs in `spec/presenters/`.
30
+ - `lib/resubject.rb` - Entry point; conditionally loads Rails integration via `defined? Rails`.
31
+
32
+ ## Conventions
33
+
34
+ - All Ruby files use `# frozen_string_literal: true`
35
+ - RuboCop is configured with `NewCops: enable` targeting Ruby 3.1
36
+ - Specs use RSpec 3; presenter specs go in `spec/presenters/` (but current specs are in `spec/resubject/`)
@@ -0,0 +1,83 @@
1
+ # Contributor Covenant 3.0 Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make our community welcoming, safe, and equitable for all.
6
+
7
+ We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
8
+
9
+ ## Encouraged Behaviors
10
+
11
+ While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
12
+
13
+ With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
14
+
15
+ 1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
16
+ 2. Engaging **kindly and honestly** with others.
17
+ 3. Respecting **different viewpoints** and experiences.
18
+ 4. **Taking responsibility** for our actions and contributions.
19
+ 5. Gracefully giving and accepting **constructive feedback**.
20
+ 6. Committing to **repairing harm** when it occurs.
21
+ 7. Behaving in other ways that promote and sustain the **well-being of our community**.
22
+
23
+ ## Restricted Behaviors
24
+
25
+ We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
26
+
27
+ 1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
28
+ 2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
29
+ 3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis of immutable identities or traits.
30
+ 4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
31
+ 5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
32
+ 6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
33
+ 7. Behaving in other ways that **threaten the well-being** of our community.
34
+
35
+ ### Other Restrictions
36
+
37
+ 1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
38
+ 2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
39
+ 3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
40
+ 4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
41
+
42
+ ## Reporting an Issue
43
+
44
+ Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
45
+
46
+ When an incident does occur, it is important to report it promptly. To report a possible violation, email <24556+felipeelias@users.noreply.github.com> or open a [GitHub Security Advisory](https://github.com/felipeelias/resubject/security/advisories/new).
47
+
48
+ Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
49
+
50
+ ## Addressing and Repairing Harm
51
+
52
+ If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
53
+
54
+ 1. Warning
55
+ 1. Event: A violation involving a single incident or series of incidents.
56
+ 2. Consequence: A private, written warning from the Community Moderators.
57
+ 3. Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
58
+ 2. Temporarily Limited Activities
59
+ 1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
60
+ 2. Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
61
+ 3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
62
+ 3. Temporary Suspension
63
+ 1. Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
64
+ 2. Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
65
+ 3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
66
+ 4. Permanent Ban
67
+ 1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
68
+ 2. Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
69
+ 3. Repair: There is no possible repair in cases of this severity.
70
+
71
+ This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
72
+
73
+ ## Scope
74
+
75
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
76
+
77
+ ## Attribution
78
+
79
+ This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
80
+
81
+ Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
82
+
83
+ For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla's code of conduct team](https://github.com/mozilla/inclusion).
data/Gemfile CHANGED
@@ -1,12 +1,14 @@
1
- ENV['rails'] ||= '4'
1
+ # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- rails_version = ENV['rails']
6
- gem 'actionpack', "~> #{rails_version}"
5
+ gemspec
6
+
7
+ rails_version = ENV.fetch('RAILS_VERSION', '8.0')
8
+ gem 'actionpack', "~> #{rails_version}.0"
7
9
 
8
- gem 'nokogiri', '~> 1.8'
10
+ gem 'rake', '>= 13.0'
9
11
  gem 'redcarpet'
10
- gem 'rubocop'
11
- # Specify your gem's dependencies in resubject.gemspec
12
- gemspec
12
+ gem 'rspec', '~> 3.0'
13
+ gem 'rubocop', '~> 1.0', require: false
14
+ gem 'yard', '~> 0.9'
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
- # Resubject
1
+ # [Resubject](https://github.com/felipeelias/resubject)
2
2
 
3
- [![Build Status](https://travis-ci.org/felipeelias/resubject.svg?branch=master)](https://travis-ci.org/felipeelias/resubject)
3
+ [![CI](https://github.com/felipeelias/resubject/actions/workflows/ci.yml/badge.svg)](https://github.com/felipeelias/resubject/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/resubject.svg)](https://badge.fury.io/rb/resubject)
5
- [![Code Climate](https://codeclimate.com/github/felipeelias/resubject/badges/gpa.svg)](https://codeclimate.com/github/felipeelias/resubject)
6
5
 
7
6
  Uber simple presenters using Ruby's SimpleDelegator.
8
7
 
@@ -11,7 +10,7 @@ Uber simple presenters using Ruby's SimpleDelegator.
11
10
  Add this line to your application's Gemfile:
12
11
 
13
12
  ```ruby
14
- gem 'resubject', '~> 0.3.0'
13
+ gem 'resubject', '~> 1.0'
15
14
  ```
16
15
 
17
16
  And then execute:
@@ -20,11 +19,7 @@ And then execute:
20
19
 
21
20
  ## Ruby/Rails versions
22
21
 
23
- This gem is compatible with Ruby 1.9 and 2.0 and Rails 3 and 4.
24
-
25
- ## Documentation
26
-
27
- Checkout the documentation in [rdoc.info/resubject](http://rdoc.info/github/felipeelias/resubject/master/frames)
22
+ This gem requires Ruby >= 3.1 and Rails >= 7.2.
28
23
 
29
24
  ## Introduction
30
25
 
@@ -239,11 +234,6 @@ By placing the file into `spec/presenters`, Resubject automatically includes the
239
234
 
240
235
  **NOTE:** Please note that the presenter is tested on isolation. It's not required but very recommended.
241
236
 
242
- ## Maintainers
243
-
244
- - Felipe Elias Philipp - [coderwall.com/felipeelias](http://coderwall.com/felipeelias)
245
- - Piotr Jakubowski - [coderwall.com/piotrj](http://coderwall.com/piotrj)
246
-
247
237
  ## Contributing
248
238
 
249
239
  1. Fork it
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
  require 'yard'
data/SECURITY.md ADDED
@@ -0,0 +1,8 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability, please report it privately
6
+ via [GitHub Security Advisories](https://github.com/felipeelias/resubject/security/advisories/new).
7
+
8
+ Please do not open a public issue for security vulnerabilities.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resubject
2
4
  module Builder
3
5
  # Raised when a invalid presenter is received
@@ -33,7 +35,7 @@ module Resubject
33
35
  # @see .present_all
34
36
  #
35
37
  def self.present(objects, template, *presenters)
36
- if objects.respond_to?(:each)
38
+ if objects.respond_to?(:to_ary)
37
39
  Builder.present_all(objects, template, *presenters)
38
40
  else
39
41
  Builder.present_one(objects, template, *presenters)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/time'
2
4
 
3
5
  module Resubject
@@ -46,17 +48,11 @@ module Resubject
46
48
  #
47
49
  # @param [Symbol] attribute the name of the attribute to be generated
48
50
  # @see http://apidock.com/rails/ActionView/Helpers/DateHelper/time_ago_in_words
49
- def time_ago(attribute, include_seconds = false)
50
- if Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new('4')
51
- define_method attribute do
52
- return if to_model.send(attribute).nil?
53
- template.time_ago_in_words to_model.send(attribute), include_seconds
54
- end
55
- else
56
- define_method attribute do
57
- return if to_model.send(attribute).nil?
58
- template.time_ago_in_words to_model.send(attribute), include_seconds: include_seconds
59
- end
51
+ def time_ago(attribute, include_seconds: false)
52
+ define_method attribute do
53
+ return if to_model.send(attribute).nil?
54
+
55
+ template.time_ago_in_words to_model.send(attribute), include_seconds: include_seconds
60
56
  end
61
57
  end
62
58
 
@@ -116,7 +112,8 @@ module Resubject
116
112
  def date_format(attribute, format = :default)
117
113
  define_method attribute do
118
114
  return if to_model.send(attribute).nil?
119
- to_model.send(attribute).to_s(format)
115
+
116
+ to_model.send(attribute).to_fs(format)
120
117
  end
121
118
  end
122
119
  end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'resubject/extensions/template_methods'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string/inflections'
2
4
 
3
5
  module Resubject
@@ -26,7 +28,7 @@ module Resubject
26
28
  presenter = "#{klass.camelize}Presenter"
27
29
 
28
30
  # Gets each constant in the namespace
29
- presenter.split('::').inject(Object) { |ns, cons| ns.const_get(cons) }
31
+ presenter.split('::').inject(Object) { |ns, cons| ns.const_get(cons, false) }
30
32
  end
31
33
  end
32
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module Resubject
@@ -86,14 +88,14 @@ module Resubject
86
88
 
87
89
  private
88
90
 
89
- def translate(*args, &block)
90
- context.t(*args, &block)
91
+ def translate(...)
92
+ context.t(...)
91
93
  end
92
94
 
93
95
  alias t translate
94
96
 
95
- def localize(*args, &block)
96
- context.l(*args, &block)
97
+ def localize(...)
98
+ context.l(...)
97
99
  end
98
100
 
99
101
  alias l localize
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resubject
2
- class Engine < ::Rails::Engine #:nodoc:
4
+ class Engine < ::Rails::Engine # :nodoc:
3
5
  initializer 'resubject.helpers' do
4
6
  ActiveSupport.on_load(:action_controller) do
5
7
  require 'resubject/rails/helpers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resubject
2
4
  # ActionController helpers
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'resubject/rails/engine'
2
4
 
3
5
  module Resubject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'resubject'
2
4
 
3
5
  module Resubject
@@ -30,11 +32,7 @@ module Resubject
30
32
  def self.included(base)
31
33
  base.instance_eval do
32
34
  let(:template) do
33
- if defined? ActionView
34
- ActionView::Base.new
35
- else
36
- mock :template
37
- end
35
+ defined?(ActionView) ? ActionView::Base.empty : double(:template)
38
36
  end
39
37
 
40
38
  subject do
@@ -45,14 +43,8 @@ module Resubject
45
43
  end
46
44
  end
47
45
 
48
- ::RSpec.configure do |c|
49
- if ::RSpec::Core::Version::STRING.split('.').first.to_i >= 3
50
- c.include Resubject::Rspec,
51
- type: :presenter,
52
- file_path: %r{spec/presenters}
53
- else
54
- c.include Resubject::Rspec,
55
- type: :presenter,
56
- example_group: { file_path: %r{spec/presenters} }
57
- end
46
+ RSpec.configure do |c|
47
+ c.include Resubject::Rspec,
48
+ type: :presenter,
49
+ file_path: %r{spec/presenters}
58
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resubject
2
- VERSION = '0.3.0'.freeze
4
+ VERSION = '1.0.0'
3
5
  end
data/lib/resubject.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'resubject/version'
2
4
  require 'resubject/naming'
3
5
  require 'resubject/extensions'
@@ -0,0 +1,12 @@
1
+ {
2
+ "bootstrap-sha": "d24ded2a6df30821c2ca9866a4e8488bd3ad3eec",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "ruby",
6
+ "package-name": "resubject",
7
+ "version-file": "lib/resubject/version.rb",
8
+ "changelog-path": "CHANGELOG.md",
9
+ "release-as": "1.0.0"
10
+ }
11
+ }
12
+ }
data/resubject.gemspec CHANGED
@@ -1,6 +1,6 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'resubject/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/resubject/version'
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = 'resubject'
@@ -12,14 +12,17 @@ Gem::Specification.new do |gem|
12
12
  gem.homepage = 'https://github.com/felipeelias/resubject'
13
13
  gem.license = 'MIT'
14
14
 
15
- gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
- gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
- gem.require_paths = ['lib']
15
+ gem.required_ruby_version = '>= 3.1'
19
16
 
20
- gem.add_dependency 'activesupport', '>= 3.2'
17
+ gem.metadata['homepage_uri'] = gem.homepage
18
+ gem.metadata['source_code_uri'] = gem.homepage
19
+ gem.metadata['changelog_uri'] = "#{gem.homepage}/blob/master/CHANGELOG.md"
20
+ gem.metadata['rubygems_mfa_required'] = 'true'
21
+
22
+ gem.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ gem.require_paths = ['lib']
21
26
 
22
- gem.add_development_dependency 'rake', '~> 12.0'
23
- gem.add_development_dependency 'rspec', '~> 3.0'
24
- gem.add_development_dependency 'yard', '~> 0.9'
27
+ gem.add_dependency 'activesupport', '>= 7.2'
25
28
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resubject
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
8
8
  - Piotr Jakubowski
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2019-04-06 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activesupport
@@ -17,56 +16,14 @@ dependencies:
17
16
  requirements:
18
17
  - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '3.2'
19
+ version: '7.2'
21
20
  type: :runtime
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
24
  - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: '3.2'
28
- - !ruby/object:Gem::Dependency
29
- name: rake
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '12.0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '12.0'
42
- - !ruby/object:Gem::Dependency
43
- name: rspec
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '3.0'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '3.0'
56
- - !ruby/object:Gem::Dependency
57
- name: yard
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
61
- - !ruby/object:Gem::Version
62
- version: '0.9'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - "~>"
68
- - !ruby/object:Gem::Version
69
- version: '0.9'
26
+ version: '7.2'
70
27
  description: Uber simple presenters
71
28
  email:
72
29
  - felipe@applicake.com
@@ -75,17 +32,26 @@ executables: []
75
32
  extensions: []
76
33
  extra_rdoc_files: []
77
34
  files:
35
+ - ".github/dependabot.yml"
36
+ - ".github/workflows/ci.yml"
37
+ - ".github/workflows/dependabot-auto-merge.yml"
38
+ - ".github/workflows/release-please.yml"
39
+ - ".github/workflows/release.yml"
78
40
  - ".gitignore"
41
+ - ".release-please-manifest.json"
79
42
  - ".rspec"
80
43
  - ".rubocop.yml"
81
- - ".travis.yml"
44
+ - ".ruby-version"
82
45
  - ".yardopts"
83
46
  - CHANGELOG.md
47
+ - CLAUDE.md
48
+ - CODE_OF_CONDUCT.md
84
49
  - Gemfile
85
50
  - IDEAS.md
86
51
  - LICENSE.txt
87
52
  - README.md
88
53
  - Rakefile
54
+ - SECURITY.md
89
55
  - lib/resubject.rb
90
56
  - lib/resubject/builder.rb
91
57
  - lib/resubject/extensions.rb
@@ -97,17 +63,16 @@ files:
97
63
  - lib/resubject/rails/helpers.rb
98
64
  - lib/resubject/rspec.rb
99
65
  - lib/resubject/version.rb
66
+ - release-please-config.json
100
67
  - resubject.gemspec
101
- - spec/resubject/builder_spec.rb
102
- - spec/resubject/extensions/template_methods_spec.rb
103
- - spec/resubject/naming_spec.rb
104
- - spec/resubject/presenter_spec.rb
105
- - spec/spec_helper.rb
106
68
  homepage: https://github.com/felipeelias/resubject
107
69
  licenses:
108
70
  - MIT
109
- metadata: {}
110
- post_install_message:
71
+ metadata:
72
+ homepage_uri: https://github.com/felipeelias/resubject
73
+ source_code_uri: https://github.com/felipeelias/resubject
74
+ changelog_uri: https://github.com/felipeelias/resubject/blob/master/CHANGELOG.md
75
+ rubygems_mfa_required: 'true'
111
76
  rdoc_options: []
112
77
  require_paths:
113
78
  - lib
@@ -115,21 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
80
  requirements:
116
81
  - - ">="
117
82
  - !ruby/object:Gem::Version
118
- version: '0'
83
+ version: '3.1'
119
84
  required_rubygems_version: !ruby/object:Gem::Requirement
120
85
  requirements:
121
86
  - - ">="
122
87
  - !ruby/object:Gem::Version
123
88
  version: '0'
124
89
  requirements: []
125
- rubyforge_project:
126
- rubygems_version: 2.7.6
127
- signing_key:
90
+ rubygems_version: 4.0.3
128
91
  specification_version: 4
129
92
  summary: Uber simple presenters using Ruby's SimpleDelegator
130
- test_files:
131
- - spec/resubject/builder_spec.rb
132
- - spec/resubject/extensions/template_methods_spec.rb
133
- - spec/resubject/naming_spec.rb
134
- - spec/resubject/presenter_spec.rb
135
- - spec/spec_helper.rb
93
+ test_files: []
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- cache: bundler
4
-
5
- rvm:
6
- - 2.4
7
- - 2.5
8
- - 2.6
9
- env:
10
- - rails=4
11
- - rails=5
@@ -1,70 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Resubject::Builder do
4
- let(:template) { double :template }
5
-
6
- before do
7
- stub_const 'Box', Class.new
8
- stub_const 'BoxPresenter', Class.new(Resubject::Presenter)
9
- stub_const 'OtherBoxPresenter', Class.new(Resubject::Presenter)
10
- end
11
-
12
- describe '.present_one' do
13
- it 'does not attempt to present nil' do
14
- presented = Resubject::Builder.present_one nil, template
15
- expect(presented).to be_nil
16
- end
17
-
18
- it 'presents the object with related class' do
19
- presented = Resubject::Builder.present_one Box.new, template
20
- expect(presented).to be_a BoxPresenter
21
- end
22
-
23
- it 'presents the object with custom class' do
24
- presented = Resubject::Builder.present_one Box.new, template, OtherBoxPresenter
25
- expect(presented).to be_a OtherBoxPresenter
26
- end
27
-
28
- it 'presents the object with multiple classes' do
29
- expect(OtherBoxPresenter).to receive(:new).twice
30
- presenters = [OtherBoxPresenter, OtherBoxPresenter]
31
- Resubject::Builder.present_one Box.new, template, *presenters
32
- end
33
-
34
- it 'raises an error if custom presenter is not a presenter' do
35
- expect do
36
- Resubject::Builder.present_one Box.new, template, nil, Class.new
37
- end.to raise_error(Resubject::Builder::InvalidPresenterArgument)
38
- end
39
- end
40
-
41
- describe '.present_all' do
42
- it 'presents multiple objects' do
43
- box = Box.new
44
- presented = Resubject::Builder.present_all [box, box], template
45
-
46
- expect(presented.map(&:class)).to eq [BoxPresenter, BoxPresenter]
47
- end
48
-
49
- it 'presents multiple objects with custom presenters' do
50
- box = Box.new
51
- presented = Resubject::Builder.present_all [box, box], template, OtherBoxPresenter
52
-
53
- expect(presented.map(&:class)).to eq [OtherBoxPresenter, OtherBoxPresenter]
54
- end
55
- end
56
-
57
- describe '.present' do
58
- it 'presents single object' do
59
- presented = Resubject::Builder.present Box.new, template
60
- expect(presented).to be_a BoxPresenter
61
- end
62
-
63
- it 'presents multiple objects' do
64
- box = Box.new
65
- presented = Resubject::Builder.present [box, box], template
66
-
67
- expect(presented.map(&:class)).to eq [BoxPresenter, BoxPresenter]
68
- end
69
- end
70
- end
@@ -1,57 +0,0 @@
1
- require 'spec_helper'
2
- require 'action_view'
3
-
4
- describe Resubject::Presenter, 'template methods' do
5
- let(:model) { double :model }
6
-
7
- let(:presenter) do
8
- Class.new(Resubject::Presenter) do
9
- currency :price
10
- time_ago :posted_at
11
- percentage :rating, precision: 0
12
- date_format :created_at, :short
13
- end
14
- end
15
-
16
- subject do
17
- presenter.new(model, ActionView::Base.new)
18
- end
19
-
20
- describe '.currency' do
21
- it 'returns currency format' do
22
- expect(model).to receive(:price).and_return(10.0)
23
- expect(subject.price).to eq '$10.00'
24
- end
25
- end
26
-
27
- describe '.time_ago' do
28
- it 'returns time ago in words' do
29
- allow(model).to receive(:posted_at).and_return(Time.now - 60 * 60)
30
- expect(subject.posted_at).to eq 'about 1 hour'
31
- end
32
-
33
- it 'returns nothing if value is nil' do
34
- allow(model).to receive(:posted_at).and_return(nil)
35
- expect(subject.posted_at).to eq nil
36
- end
37
- end
38
-
39
- describe '.percentage' do
40
- it 'returns formatted percentage' do
41
- allow(model).to receive(:rating).and_return(95.123)
42
- expect(subject.rating).to eq '95%'
43
- end
44
- end
45
-
46
- describe '.date_format' do
47
- it 'returns formatted date' do
48
- allow(model).to receive(:created_at).and_return(Time.at(1_358_082_653).utc)
49
- expect(subject.created_at).to eq '13 Jan 13:10'
50
- end
51
-
52
- it 'returns nothing if value is nil' do
53
- allow(model).to receive(:created_at).and_return(nil)
54
- expect(subject.created_at).to eq nil
55
- end
56
- end
57
- end
@@ -1,35 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Resubject::Naming do
4
- before do
5
- stub_const 'Box', Class.new
6
- stub_const 'BoxPresenter', Class.new
7
- end
8
-
9
- it 'gets the presenter class from an object' do
10
- presenter = Resubject::Naming.presenter_for Box.new
11
-
12
- expect(presenter).to eq BoxPresenter
13
- end
14
-
15
- it 'gets the presenter class from a symbol' do
16
- presenter = Resubject::Naming.presenter_for :box
17
-
18
- expect(presenter).to eq BoxPresenter
19
- end
20
-
21
- it 'gets the presenter class from a string' do
22
- presenter = Resubject::Naming.presenter_for 'box'
23
-
24
- expect(presenter).to eq BoxPresenter
25
- end
26
-
27
- it 'gets namespaced constants' do
28
- stub_const 'Namespaced', Class.new
29
- stub_const 'Namespaced::BoxPresenter', Class.new
30
-
31
- presenter = Resubject::Naming.presenter_for 'Namespaced::Box'
32
-
33
- expect(presenter).to eq Namespaced::BoxPresenter
34
- end
35
- end
@@ -1,69 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Resubject::Presenter do
4
- let(:object) { double :object }
5
-
6
- subject { Resubject::Presenter.new(object) }
7
-
8
- it 'delegate methods to object' do
9
- expect(object).to receive(:pretty)
10
- subject.pretty
11
- end
12
-
13
- describe :to_model do
14
- it 'returns the delegated object' do
15
- expect(subject.to_model).to eq object
16
- end
17
- end
18
-
19
- describe '.all' do
20
- it 'creates various instances of the presenter' do
21
- presented = Resubject::Presenter.all([object, object])
22
-
23
- expect(presented.map(&:class)).to eq [Resubject::Presenter,
24
- Resubject::Presenter]
25
- end
26
- end
27
-
28
- describe :present do
29
- it 'creates a presenter' do
30
- stub_const 'Box', Class.new
31
- stub_const 'BoxPresenter', Class.new(Resubject::Presenter)
32
-
33
- presented = subject.present Box.new
34
- expect(presented).to be_a BoxPresenter
35
- end
36
- end
37
-
38
- describe '.presents' do
39
- let :presenter do
40
- Class.new(Resubject::Presenter) do
41
- presents :item
42
- presents :other_item, ItemPresenter
43
- end
44
- end
45
-
46
- before do
47
- stub_const 'Item', Class.new
48
- stub_const 'ItemPresenter', Class.new(Resubject::Presenter)
49
- end
50
-
51
- it 'generates a method preseting the attribute' do
52
- box = double :box, item: Item.new
53
-
54
- expect(presenter.new(box).item).to be_a ItemPresenter
55
- end
56
-
57
- it 'does not attempt to find a class from nil' do
58
- box = double :box, item: nil
59
-
60
- expect(presenter.new(box).item).to be_nil
61
- end
62
-
63
- it 'presents the attributes with custom presenter' do
64
- box = double :box, other_item: double(:other)
65
-
66
- expect(presenter.new(box).other_item).to be_a ItemPresenter
67
- end
68
- end
69
- end
data/spec/spec_helper.rb DELETED
@@ -1,2 +0,0 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
- require 'resubject'