diff --git a/.github/workflows/ruby_on_rails.yml b/.github/workflows/ruby_on_rails.yml
index a40908be6..360843e1d 100644
--- a/.github/workflows/ruby_on_rails.yml
+++ b/.github/workflows/ruby_on_rails.yml
@@ -22,6 +22,7 @@ jobs:
ruby-version: ${{ matrix.ruby_version }}
- name: Build and test with Rake
run: |
+ sudo apt-get install libsqlite3-dev
gem install bundler:1.14.0
bundle update
bundle install --jobs 4 --retry 3
diff --git a/.gitignore b/.gitignore
index cc6dfae43..70eeeab0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,5 +47,7 @@ build-iPhoneSimulator/
# .ruby-version
# .ruby-gemset
+/**/*.sqlite3
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
diff --git a/Gemfile b/Gemfile
index 1123388c7..d96d85cf0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,3 +6,4 @@ gemspec
rails_version = "#{ENV['RAILS_VERSION'] || '6.0.0'}"
gem "rails", rails_version == "master" ? { github: "rails/rails" } : rails_version
+gem "sqlite3", rails_version >= "6.0.0" ? ">= 1.4.0" : "< 1.4.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index ee044664b..6b1fa5b4a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -146,6 +146,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
+ sqlite3 (1.4.1)
temple (0.8.1)
thor (0.20.3)
thread_safe (0.3.6)
@@ -171,6 +172,7 @@ DEPENDENCIES
rubocop (= 0.74)
rubocop-github (~> 0.13.0)
slim (~> 4.0)
+ sqlite3 (>= 1.4.0)
BUNDLED WITH
1.17.3
diff --git a/README.md b/README.md
index c4e56cbb7..0c049cd45 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,24 @@ Components can be rendered via:
`render(component: TestComponent, locals: { foo: :bar })`
+**Rendering components through models**
+
+Passing model instances will cause `render` to look for its respective component class.
+
+The component is instantiated with the rendered model instance.
+
+Example for a `Post` model:
+
+`render(@post)`
+
+```ruby
+class PostComponent < ActionView::Component
+ def initialize(post)
+ @post = post
+ end
+end
+```
+
The following syntax has been deprecated and will be removed in v2.0.0:
`render(TestComponent.new(foo: :bar))`
diff --git a/lib/action_view/component.rb b/lib/action_view/component.rb
index b750163e5..e51e25b5a 100644
--- a/lib/action_view/component.rb
+++ b/lib/action_view/component.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require "action_view/component/monkey_patch"
+require "action_view/component/active_model_conversion_monkey_patch"
require "action_view/component/base"
require "action_view/component/railtie"
diff --git a/lib/action_view/component/active_model_conversion_monkey_patch.rb b/lib/action_view/component/active_model_conversion_monkey_patch.rb
new file mode 100644
index 000000000..dd84192a7
--- /dev/null
+++ b/lib/action_view/component/active_model_conversion_monkey_patch.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module ActiveModel
+ module Conversion
+ def to_component_class
+ "#{self.class.name}Component".safe_constantize
+ end
+ end
+end
diff --git a/lib/action_view/component/monkey_patch.rb b/lib/action_view/component/monkey_patch.rb
index 8e213c9db..41460b372 100644
--- a/lib/action_view/component/monkey_patch.rb
+++ b/lib/action_view/component/monkey_patch.rb
@@ -17,6 +17,8 @@ def render(options = {}, args = {}, &block)
options.new(args).render_in(self, &block)
elsif options.is_a?(Hash) && options.has_key?(:component)
options[:component].new(options[:locals]).render_in(self, &block)
+ elsif options.respond_to?(:to_component_class) && !options.to_component_class.nil?
+ options.to_component_class.new(options).render_in(self, &block)
else
super
end
diff --git a/test/action_view/component_test.rb b/test/action_view/component_test.rb
index a9bf38ce6..c5ac22707 100644
--- a/test/action_view/component_test.rb
+++ b/test/action_view/component_test.rb
@@ -251,6 +251,13 @@ def test_renders_component_with_rb_in_its_name
assert_equal "Editorb!\n", render_inline(EditorbComponent).text
end
+ def test_to_component_class
+ post = Post.new(title: "Awesome post")
+
+ assert_equal PostComponent, post.to_component_class
+ assert_equal "The Awesome post component!", render_inline(post).first.to_html
+ end
+
private
def modify_file(file, content)
diff --git a/test/app/components/post_component.html.erb b/test/app/components/post_component.html.erb
new file mode 100644
index 000000000..5cb65df70
--- /dev/null
+++ b/test/app/components/post_component.html.erb
@@ -0,0 +1 @@
+The <%= @post.title %> component!
diff --git a/test/app/components/post_component.rb b/test/app/components/post_component.rb
new file mode 100644
index 000000000..2d87e4b02
--- /dev/null
+++ b/test/app/components/post_component.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class PostComponent < ActionView::Component::Base
+ def initialize(post)
+ @post = post
+ end
+end
diff --git a/test/app/models/application_record.rb b/test/app/models/application_record.rb
new file mode 100644
index 000000000..71fbba5b3
--- /dev/null
+++ b/test/app/models/application_record.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/test/app/models/post.rb b/test/app/models/post.rb
new file mode 100644
index 000000000..a9c55bd6f
--- /dev/null
+++ b/test/app/models/post.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+class Post < ApplicationRecord
+end
diff --git a/test/config/application.rb b/test/config/application.rb
index ce4a1906a..9279698b0 100644
--- a/test/config/application.rb
+++ b/test/config/application.rb
@@ -2,6 +2,7 @@
require File.expand_path("../boot", __FILE__)
+require "active_record/railtie"
require "active_model/railtie"
require "action_controller/railtie"
require "action_view/railtie"
diff --git a/test/config/database.yml b/test/config/database.yml
new file mode 100644
index 000000000..febf936bc
--- /dev/null
+++ b/test/config/database.yml
@@ -0,0 +1,8 @@
+default: &default
+ adapter: sqlite3
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ timeout: 5000
+
+test:
+ <<: *default
+ database: db/test.sqlite3
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 76a2127fe..11567425e 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -16,3 +16,11 @@
def trim_result(html)
html.delete(" \t\r\n")
end
+
+ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
+
+ActiveRecord::Schema.define do
+ create_table :posts, force: true do |t|
+ t.string :title
+ end
+end