Skip to content
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

Action Pack Variants #12977

Merged
merged 2 commits into from Dec 3, 2013
Merged

Action Pack Variants #12977

merged 2 commits into from Dec 3, 2013

Conversation

lukaszx0
Copy link
Member

By default, variants in the templates will be picked up if a variant is set and
there's a match. The format will be:

  app/views/projects/show.html.erb
  app/views/projects/show.html+tablet.erb
  app/views/projects/show.html+phone.erb

If request.variant = :tablet is set, we'll automatically be rendering the html+tablet template.

In the controller, we can also tailer to the variants with this syntax:

  class ProjectsController < ActionController::Base
    def show
      respond_to do |format|
        format.html do |html|
          @stars = @project.stars

          html.tablet { @notifications = @project.notifications }
          html.phone  { @chat_heads    = @project.chat_heads }
        end

        format.js
        format.atom
      end
    end
  end

The variant itself is nil by default, but can be set in before filters, like so:

  class ApplicationController < ActionController::Base
    before_action do
      if request.user_agent =~ /iPad/
        request.variant = :tablet
      end
    end
  end

This is modeled loosely on custom mime types, but it's specifically not intended
to be used together. If you're going to make a custom mime type, you don't need
a variant. Variants are for variations on a single mime types.

/cc @dhh @josevalim

@@ -57,6 +57,10 @@ def _normalize_options(options) #:nodoc:
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
require 'pry';binding.pry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a debug statement left in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shit, I knew I've missed something. Thanks! ;)

@lukaszx0
Copy link
Member Author

Thanks for the review, it's very valuable for me! <3

I've just pushed commit with fixes addressing your comments (separate commit to have nice diff, i'll squash it before merging). I've left some open questions questions and didn't moved setting variant from _normalize_options as I don't know yet how to do it nicely and don't have time right now to continue working on this. Will be back working on this later today!

@ghost ghost assigned lukaszx0 Nov 21, 2013
@lukaszx0
Copy link
Member Author

While polishing this PR I've stuck with the very last thing - nice a logical place to set variant in lookup context (#12977 (comment)). Suprisingly for various reasons it's not that straight forward as one would expect. I got suggestion by @josevalim to change implementation a bit and instead of setting it in request object (request.variant) move this to controller itself and simply delegate this to lookup context (in action: self.variant). The only drawback here is that we won't be able to set variant in middleware easily, however we can overcome this issues coping request.variant value to self.variant at the very beginning.

@dhh @jeremy What do you think?

@dhh
Copy link
Member

dhh commented Nov 25, 2013

I'd prefer to have it set the same way that format is set. It's an extension to the format, and the format is set on the request. I don't think we need to change this. If anything, self.variant is going to be less clear IMO. Request#variant implies that this is a per request setting as well.

On Nov 25, 2013, at 2:21, Łukasz Strzałkowski notifications@github.com wrote:

While polishing this PR I've stuck with the very last thing - nice a logical place to set variant in lookup context (#12977 (comment)). Suprisingly for various reasons it's not that straight forward as one would expect. I got suggestion by @josevalim to change implementation a bit and instead of setting it in request object (request.variant) move this to controller itself and simply delegate this to lookup context (in action: self.variant). The only drawback here is that we won't be able to set variant in middleware easily, however we can overcome this issues coping request.variant value to self.variant at the very beginning.

@dhh @jeremy What do you think?


Reply to this email directly or view it on GitHub.

@jeremy
Copy link
Member

jeremy commented Nov 25, 2013

Ditto. The request format has a well traveled path from Rack env to middleware to controller action to view resolver. The request variant walks the same path. It the variant diverges and needs some special handling, that's a strong sign that we should double-check why and adjust course so format & variant continue to behave alike.

From the implementation so far, it looks like your sticking point may be due to seeing that request formats are set on the lookup context before processing an action: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/rendering.rb#L5-L9 If the variant is set before processing, then you can't set the request.variant within an action. No good.

Trouble is, the way we manage the format today is pretty awkward. We set it on the lookup context (aka, the controller) because we don't want the view to "know about" requests. Fine, ok. To fix, set the format and variant on the lookup context just before rendering. The likely candidate for that is _normalize_render.

@lukaszx0
Copy link
Member Author

Thanks guys. I agree with you, it was Jose's suggestion to overcome problem with finding nice place to inject this functionality.

@jeremy nicely described what's going on and suggested _normalize_render. So if the _normalize_render is a blessed place, it means that we've made a circle and end up where we begun - the initial commit, where variant was set in _normalize_options (which is called by _normalize_render: https://github.com/rails/rails/blob/master/actionpack/lib/abstract_controller/rendering.rb#L103-L107).

The best place for this to happen, in my opinion would be _process_format, done in here: lukaszx0@630e853#diff-7f9aef6572a262683ec9c1fda47118ebL37. However with this change, implicit rendering with variant set (possibly the most common use case) won't work, this needs to be set before this line: https://github.com/rails/rails/blob/master/actionpack/lib/abstract_controller/rendering.rb#L22 (like _normalize_render), not after.

Can it stay the way it was initially proposed then 😉 ?

@jeremy
Copy link
Member

jeremy commented Nov 25, 2013

_process_format is called after rendering, no?

_normalize_options is specifically purposed to taking an incoming options has and normalize it into an outgoing one. Introducing new options into the normalized hash is out of scope. It is in scope for _normalize_render though.

@lukaszx0
Copy link
Member Author

I've rebased and force pushed commit with all comments addressed.

If there's no other issues, I'll happily merge this :shipit:

@lukaszx0
Copy link
Member Author

@jeremy regarding defined?(request). I'm afraid we need to keep it. We're using request in AbstractController which, as name suggest, is abstract thus in rails world it's either base of ActionController which provides request or ActionMailer which does not. I agree it doesn't look very nice, but I think it needs to stay.
Here's diff with attempt to remove this check which made me realize that it should stay - https://gist.github.com/strzalek/68b407edc52b608fb797 What do you think?

@rafaelfranca removed additional hash lookup and added docs 🍻

# app/views/projects/show.html+tablet.erb
# app/views/projects/show.html+phone.erb
#
# If `request.variant = :tablet` is set, we'll automatically be rendering the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rdoc so will not work, it should berequest.variant = :tablet`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

# end
# end
#
# This is modeled loosely on custom mime types, but it's specifically not
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems an implementation detail, maybe we should omit this first sentence and keep others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I actually think we should either leave it or remove whole paragraph. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to remove the whole paragraph.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too 👍

@lukaszx0
Copy link
Member Author

Thanks @rafaelfranca, I've just pushed fixes to docs.

@lukaszx0
Copy link
Member Author

:shipit:

If request.variant = :tablet is set, we'll automatically be rendering the
html+tablet template.

In the controller, we can also tailer to the variants with this syntax:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the controller, can we respond to variants just like we respond to formats:

@lukaszx0
Copy link
Member Author

@@ -102,6 +102,7 @@ def _process_format(format)
# :api: private
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
options[:variant] = request.variant if defined?(request) && request.variant.present?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need this defined? check right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid we need: #12977 (comment)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are leaving this here, I'd leave a comment/todo do remove it in the future, as @rafaelfranca commented, it seems kinda weird having to do this check if we are inside action pack.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! I've just done that :)

On Tue, Dec 3, 2013 at 12:38 PM, Carlos Antonio da Silva <
notifications@github.com> wrote:

In actionpack/lib/abstract_controller/rendering.rb:

@@ -102,6 +102,7 @@ def _process_format(format)
# :api: private
def _normalize_render(_args, &block)
options = _normalize_args(_args, &block)

  •  options[:variant] = request.variant if defined?(request) && request.variant.present?
    

If we are leaving this here, I'd leave a comment/todo do remove it in the
future, as @rafaelfranca https://github.com/rafaelfranca commented, it
seems kinda weird having to do this check if we are inside action pack.


Reply to this email directly or view it on GitHubhttps://github.com//pull/12977/files#r8057129
.

@lukaszx0
Copy link
Member Author

lukaszx0 commented Dec 3, 2013

@dhh @jeremy I've just pushed updates 🚢

#
# You can set the variant in a before_action:
#
# request.variant = :tablet if request.user_agent =~ /iPad/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be 2 spaces here too :)

By default, variants in the templates will be picked up if a variant is set
and there's a match. The format will be:

  app/views/projects/show.html.erb
  app/views/projects/show.html+tablet.erb
  app/views/projects/show.html+phone.erb

If request.variant = :tablet is set, we'll automatically be rendering the
html+tablet template.

In the controller, we can also tailer to the variants with this syntax:

  class ProjectsController < ActionController::Base
    def show
      respond_to do |format|
        format.html do |html|
          @stars = @project.stars

          html.tablet { @notifications = @project.notifications }
          html.phone  { @chat_heads    = @project.chat_heads }
        end

        format.js
        format.atom
      end
    end
  end

The variant itself is nil by default, but can be set in before filters, like
so:

  class ApplicationController < ActionController::Base
    before_action do
      if request.user_agent =~ /iPad/
        request.variant = :tablet
      end
    end
  end

This is modeled loosely on custom mime types, but it's specifically not
intended to be used together. If you're going to make a custom mime type,
you don't need a variant. Variants are for variations on a single mime
types.
jeremy added a commit that referenced this pull request Dec 3, 2013
@jeremy jeremy merged commit 501acab into rails:master Dec 3, 2013
@jeremy
Copy link
Member

jeremy commented Dec 3, 2013

❤️

@aag1091
Copy link

aag1091 commented Dec 24, 2013

<3 thank you @strzalek

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants