New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorate associated objects #68

Merged
merged 1 commit into from Feb 7, 2017
Jump to file or symbol
Failed to load files and symbols.
+148 −45
Diff settings

Always

Just for now

View
@@ -65,48 +65,6 @@ end
<% end %>
```
## Decorating associated objects ##
ActiveDecorator *does not* automatically decorate associated objects. We recommend that you pass associated objects to `render` when decorated associated objects are needed.
```ruby
# app/models/blog_post.rb
class BlogPost < ActiveRecord::Base
# published_at:datetime
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :blog_posts
end
# app/decorators/blog_post_decorator.rb
module BlogPostDecorator
def published_date
published_at.strftime("%Y.%m.%d")
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
@users = User.all
end
end
```
```erb
# app/views/users/index.html.erb
<% @users.each do |user| %>
<%= render partial: "blog_post", locals: { blog_posts: user.blog_posts } %><br>
<% end %>
# app/views/users/_blog_post.html.erb
<% blog_posts.each do |blog_post| %>
<%= blog_post.published_date %>
<% end %>
```
## Testing
You can test a decorator using your favorite test framework by decorating the model instance with
@@ -0,0 +1,2 @@
module ActiveDecorator::Decorated
end
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'singleton'
require 'active_decorator/helpers'
require 'active_decorator/decorated'
module ActiveDecorator
class Decorator
@@ -28,13 +29,21 @@ def decorate(obj)
obj.extend ActiveDecorator::RelationDecoratorLegacy unless obj.is_a? ActiveDecorator::RelationDecoratorLegacy
end
else
if defined?(ActiveRecord) && obj.is_a?(ActiveRecord::Base) && !obj.is_a?(ActiveDecorator::Decorated)
obj.extend ActiveDecorator::Decorated
end
d = decorator_for obj.class
return obj unless d
obj.extend d unless obj.is_a? d
obj
end
end
def decorate_association(owner, target)
owner.is_a?(ActiveDecorator::Decorated) ? decorate(target) : target
end
private
def decorator_for(model_class)
return @@decorators[model_class] if @@decorators.key? model_class
@@ -0,0 +1,27 @@
# frozen_string_literal: true
module ActiveDecorator
module Monkey
module ActiveRecord
module Associations
module Association
def target
ActiveDecorator::Decorator.instance.decorate_association(owner, super)
end
end
module CollectionAssociation
# @see https://github.com/rails/rails/commit/03855e790de2224519f55382e3c32118be31eeff
if Rails.version.to_f < 4.1
def first_or_last(*args)
ActiveDecorator::Decorator.instance.decorate_association(owner, super)
end
else
def first_nth_or_last(*args)
ActiveDecorator::Decorator.instance.decorate_association(owner, super)
end
end
end
end
end
end
end
@@ -28,6 +28,12 @@ class Railtie < ::Rails::Railtie
ActionMailer::Base.send :include, ActiveDecorator::ViewContext::Filter
end
end
ActiveSupport.on_load :active_record do
require 'active_decorator/monkey/active_record/associations'
ActiveRecord::Associations::Association.send :prepend, ActiveDecorator::Monkey::ActiveRecord::Associations::Association
ActiveRecord::Associations::CollectionAssociation.send :prepend, ActiveDecorator::Monkey::ActiveRecord::Associations::CollectionAssociation
end
end
end
end
@@ -1,2 +1,9 @@
<%= @author.name %>
<%= @author.capitalized_name %>
<%= @author.books.first.upcased_title %>
<%= @author.books[-1].upcased_title %>
<% if p = @author.publishers.last %><%= p.upcased_name %><% end %>
<% if p = @author.profile %><%= p.address %><% end %>
<% if h = @author.profile_history %><%= h.update_date %><% end %>
<% if m = @author.magazines.first %><%= m.upcased_title %><% end %>
<% if c = @author.company %><%= c.reverse_name %><% end %>
@@ -1 +1,2 @@
<%= @movie.name %>
<% if a = @movie.author %><%= a.reverse_name %><% end %>
View
@@ -46,13 +46,39 @@ class Application < Rails::Application
# models
class Author < ActiveRecord::Base
has_many :books
has_many :publishers, through: :books
has_many :movies
has_one :profile
has_one :profile_history, through: :profile
has_and_belongs_to_many :magazines
belongs_to :company
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
class Novel < Book
end
class Movie < ActiveRecord::Base
belongs_to :author
end
class Publisher < ActiveRecord::Base
has_many :books
end
class Profile < ActiveRecord::Base
belongs_to :author
has_one :profile_history
accepts_nested_attributes_for :profile_history
end
class ProfileHistory < ActiveRecord::Base
belongs_to :profile
end
class Magazine < ActiveRecord::Base
has_and_belongs_to_many :authors
end
class Company < ActiveRecord::Base
has_many :authors
end
# helpers
@@ -93,6 +119,31 @@ def error
"ERROR"
end
end
module PublisherDecorator
def upcased_name
name.upcase
end
end
module ProfileDecorator
def address
"secret"
end
end
module ProfileHistoryDecorator
def update_date
updated_on.strftime('%Y/%m/%d')
end
end
module MagazineDecorator
def upcased_title
title.upcase
end
end
module CompanyDecorator
def reverse_name
name.reverse
end
end
# decorator fake
class MovieDecorator; end
@@ -178,9 +229,15 @@ def thanks(book)
# migrations
class CreateAllTables < ActiveRecord::Migration
def self.up
create_table(:authors) {|t| t.string :name}
create_table(:books) {|t| t.string :title; t.references :author; t.string :type }
create_table(:movies) {|t| t.string :name}
create_table(:authors) {|t| t.string :name; t.references :company}
create_table(:books) {|t| t.string :title; t.string :type; t.references :author; t.references :publisher }
create_table(:profiles) {|t| t.string :address; t.references :author}
create_table(:profile_histories) {|t| t.date :updated_on; t.references :profile}
create_table(:publishers) {|t| t.string :name}
create_table(:movies) {|t| t.string :name; t.references :author}
create_table(:magazines) {|t| t.string :title}
create_table(:authors_magazines) {|t| t.references :author; t.references :magazine}
create_table(:companies) {|t| t.string :name}
end
end
@@ -0,0 +1,36 @@
require 'test_helper'
class AssociationTest < ActionDispatch::IntegrationTest
setup do
company = Company.create! name: 'NaCl'
@matz = company.authors.create! name: 'matz'
@matz.books.create!(
title: 'the world of code',
publisher_attributes: { name: 'nikkei linux' }
)
@matz.books.create!(
title: 'the ruby programming language',
publisher_attributes: { name: "o'reilly" }
)
@matz.create_profile! address: 'Matsue city, Shimane'
@matz.profile.create_profile_history! updated_on: Date.new(2017, 2, 7)
@matz.magazines.create! title: 'rubima'
end
test 'decorating associated objects' do
visit "/authors/#{@matz.id}"
assert page.has_content? 'the world of code'.upcase
assert page.has_content? 'the ruby programming language'.upcase
assert page.has_content? "o'reilly".upcase
assert page.has_content? 'secret'
assert page.has_content? '2017/02/07'
assert page.has_content? 'rubima'.upcase
assert page.has_content? 'NaCl'.reverse
end
test "decorating associated objects that owner doesn't have decorator" do
movie = Movie.create! author: @matz
visit "/movies/#{movie.id}"
assert page.has_content? 'matz'.reverse
end
end
ProTip! Use n and p to navigate between commits in a pull request.