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

Merge multi_fetch_fragments. #18948

Merged
merged 3 commits into from Feb 25, 2015
Merged

Conversation

@kaspth
Copy link
Member

kaspth commented Feb 15, 2015

Fixes #18900.

Makes caching a collection of template partials faster using read_multi
on the Rails cache store.

Some caching implementations have optimized read_multi so we don't have
to check in the cache store for every template.

@dhh, I will be tackling automatic caching next. This is just so my approach and choices, so far, can be reviewed.

@dhh
Copy link
Member

dhh commented Feb 15, 2015

Seems like a legit approach to me 👍. Much smoother implementation than the original as well.

@kaspth
Copy link
Member Author

kaspth commented Feb 15, 2015

Glad to hear it 👍

Given your thoughts on having the caching be automatic, do you want to keep the support for a custom cache key?

<%= render partial: 'item', collection: @items, cache: ->(item) { [item, 'show'] } %>
@dhh
Copy link
Member

dhh commented Feb 15, 2015

Yeah, I think that case still makes sense. But when you’re using the custom cache key like this, it should ignore the specific <% cache %> calls within the partials.

I think using a custom cache key is going to be the minority case, so focusing on the default case first makes sense.

On Feb 15, 2015, at 1:07 PM, Kasper Timm Hansen notifications@github.com wrote:

Glad to hear it

Given your thoughts on having the caching be automatic, do you want to keep the support for a custom cache key?

<%= render partial: 'item', collection: @Items, cache: ->(item) { [item, 'show'] } %>

Reply to this email directly or view it on GitHub #18948 (comment).

@kaspth
Copy link
Member Author

kaspth commented Feb 15, 2015

Got it 👍

@kaspth kaspth force-pushed the kaspth:automatic-collection-caching branch Feb 15, 2015
@kaspth
kaspth reviewed Feb 15, 2015
View changes
actionview/lib/action_view/railtie.rb Outdated
@@ -33,6 +33,8 @@ class Railtie < Rails::Railtie # :nodoc:
if app.config.action_view.cache_template_loading.nil?
ActionView::Resolver.caching = app.config.cache_classes
end

PartialRenderer.collection_cache = Rails.cache

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 15, 2015

Author Member

The Railties tests fail because PartialRenderer doesn't respond to collection_cache= here. But I don't understand why it doesn't.

PartialRenderer is set to be autoloaded, so this reference should require CollectionCaching and include it into the renderer. I'm all ears if anybody has any ideas.

This comment has been minimized.

Copy link
@georgeclaghorn

georgeclaghorn Feb 16, 2015

Member

You've declared collection_cache to be a mattr_reader. There doesn't seem to be a corresponding writer.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

Do'h! Thanks 😄

Kasper

Den 16/02/2015 kl. 07.04 skrev George Claghorn notifications@github.com:

In actionview/lib/action_view/railtie.rb:

@@ -33,6 +33,8 @@ class Railtie < Rails::Railtie # :nodoc:
if app.config.action_view.cache_template_loading.nil?
ActionView::Resolver.caching = app.config.cache_classes
end
+

  •    PartialRenderer.collection_cache = Rails.cache
    
    You've declared collection_cache to be a mattr_reader. There doesn't seem to be a corresponding writer.


Reply to this email directly or view it on GitHub.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

I ditched the initializer and used Rails.try(:cache) in the collection_cache definition. I still changed it to a mattr_accessor, so Action View users outside of Rails can switch cache store if the in memory one isn't a fit.

@georgeclaghorn
georgeclaghorn reviewed Feb 16, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated

def expanded_cache_key(key)
caching_controller.fragment_cache_key(@view.cache_fragment_name(key)).tap do |k|
return k.dup if k.frozen? # #read_multi & #write may require mutability, Dalli 2.6.0.

This comment has been minimized.

Copy link
@georgeclaghorn

georgeclaghorn Feb 16, 2015

Member

The tap seems unnecessary when we'll always return from inside it:

def expanded_cache_key(key)
  expanded_key = caching_controller.fragment_cache_key(@view.cache_fragment_name(key))
  expanded_key.dup if expanded_key.frozen?
end

This comment has been minimized.

Copy link
@gsamokovarov

gsamokovarov Feb 16, 2015

Contributor

This will return nil if the expanded_key isn't frozen.

This comment has been minimized.

Copy link
@georgeclaghorn

georgeclaghorn Feb 16, 2015

Member

Whoops, right.

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

Why does Dalli mutate the key?

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

Agree that doing an explicit return within a #tap is confusing, since tap is designed to do just the opposite: return the receiver. Clearer to take its return value and do an explicit conditional: v = …; v.frozen? ? v.dup : v

This comment has been minimized.

Copy link
@kaspth

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

It's duping the key before forcing encoding, though

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

Are you saying we should remove our dup here?

Kasper

Den 17/02/2015 kl. 21.15 skrev Jeremy Kemper notifications@github.com:

In actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb:

  •  end
    
  • end
  • def cache_collection?
  •  ActionController::Base.perform_caching && (@options[:cache].present? || @locals[:cache].present?)
    
  • end
  • def collection_by_cache_keys
  •  seed = @options[:cache].respond_to?(:call) ? @options[:cache] : ->(i) { i }
    
  •  Hash[ @collection.map { |item| [expanded_cache_key(seed.call(item)), item] } ]
    
  • end
  • def expanded_cache_key(key)
  •  caching_controller.fragment_cache_key(@view.cache_fragment_name(key)).tap do |k|
    
  •    return k.dup if k.frozen? # #read_multi & #write may require mutability, Dalli 2.6.0.
    
    It's duping the key before forcing encoding, though


Reply to this email directly or view it on GitHub.

@georgeclaghorn
Copy link
Member

georgeclaghorn commented Feb 16, 2015

Awesome, @kaspth!

@kaspth kaspth force-pushed the kaspth:automatic-collection-caching branch 2 times, most recently Feb 16, 2015
@kaspth
Copy link
Member Author

kaspth commented Feb 16, 2015

@georgeclaghorn Thanks for the review 😄

@dhh I've pushed a commit that does a multi_read even if cache: true isn't passed to the collection and the template starts with a cache call with a name that matches the as option or what we infer it to be.

How close is this to what you want?

@dhh
Copy link
Member

dhh commented Feb 16, 2015

@kaspth So when we hit a render collection call, you look in the partial to see if there's a <% cache call, right? Because we don't want all render collection to obviously cache unless the partials have that call. If that's the way it is, then that sounds like the way it should be!

And thanks for getting this working so quickly. Very excited to try this in my latest app!

@kaspth
Copy link
Member Author

kaspth commented Feb 16, 2015

@morgoth
morgoth reviewed Feb 16, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated
included do
# Use Rails.cache or the fallback cache store if Action View is used without Rails.
mattr_accessor(:collection_cache) do
Rails.try(:cache) || ActiveSupport::Cache::MemoryStore.new

This comment has been minimized.

Copy link
@morgoth

morgoth Feb 16, 2015

Member

Maybe this could be set in railtie? Not sure if there is a Rails defined if actionview is used standalone.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

You're right. I had it as a railtie first, but then I switched to this. I should have just checked the console:

kaspth:action view kasperhansen$ irb -raction_view
irb(main):001:0> Rails
NameError: uninitialized constant Rails

This comment has been minimized.

Copy link
@simi

simi Feb 16, 2015

Contributor

What about to move Rails.cache to ActiveSupport.cache (and all configs and stuff) and just delegate it from Rails.cache there? So this line will change to ActiveSupport.cache and default storage will be chosen there?

Does it make sense? Would be this PR accepted?

This comment has been minimized.

Copy link
@dhh

dhh Feb 16, 2015

Member

I don’t think we should be hanging off globals in ActiveSupport. Rails is the global context for this stuff.

On Feb 16, 2015, at 11:22, Josef Šimánek notifications@github.com wrote:

In actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb:

@@ -0,0 +1,74 @@
+require 'active_support/core_ext/object/try'
+
+module ActionView

  • module CollectionCaching
  • extend ActiveSupport::Concern
  • included do
  •  # Use Rails.cache or the fallback cache store if Action View is used without Rails.
    
  •  mattr_accessor(:collection_cache) do
    
  •    Rails.try(:cache) || ActiveSupport::Cache::MemoryStore.new
    

What about to move Rails.cache to ActiveSupport.cache (and all configs and stuff) and just delegate it from Rails.cache there? So this line will change to ActiveSupport.cache and default storage will be chosen there?

Does it make sense? Would be this PR accepted?


Reply to this email directly or view it on GitHub.

@kaspth kaspth added the actionview label Feb 16, 2015
@kaspth kaspth added this to the 5.0.0 milestone Feb 16, 2015
@kaspth kaspth self-assigned this Feb 16, 2015
@kaspth kaspth force-pushed the kaspth:automatic-collection-caching branch Feb 16, 2015
@kaspth
kaspth reviewed Feb 16, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated
end

def inferred_cache_name
@options[:as] || @template.virtual_path.split('/').last.sub('_', '')

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

The documentation says virtual_path isn't set for inline templates: https://github.com/rails/rails/blob/master/actionview/lib/action_view/template.rb#L162.

Can someone smarter than me say if an inline template would get this far in the eligibility logic?

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

Collection rendering is for partials only, so a render inline: … won't ever be here.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

Great 👍

@kaspth
kaspth reviewed Feb 16, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated
end

def collection_only_renders_one_template?
@template # Template is only set when a collection renders one template.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

@dhh When you render a collection, do you render have it render different templates? Say a todo list renders todo and amazing_todo?

If so, we can't use read_multi there.

Because @template is only set if there's a @path and @path is only set if the collection renders a single template.

Eventually collection_without_template is called and we don't have access to where it finds templates.

We could try and patch it in there, but then we'd fetch the templates twice. I'll admit I don't know how costly that is. But I'd rather not 😄

This comment has been minimized.

Copy link
@dhh

dhh Feb 16, 2015

Member

It'd be awesome if it would use read_multi PER template type was being fetched, but that can be a second step. So say you have 5 amazing todos and 3 regular todos -- that'd be 2 read_multi's. One per type.

This comment has been minimized.

Copy link
@dhh

dhh Feb 16, 2015

Member

Which actually leads me to a separate point. <%= render @collection_with_different_types %> is not something we currently have a way of doing by hand, if your partials don't map to the RecordIdentifier lookup. Need a way to do that.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 16, 2015

Author Member

Your last comment is a little too inside baseball for me 😄 I do recognize something about an identifier, but I've only looked at Action View for two days so it's pretty fuzzy.

This comment has been minimized.

Copy link
@dhh

dhh Feb 16, 2015

Member

Haha, I'll explain. The template system includes a digest of the template file in all <% cache %> calls. This makes it such that if you change the template file, it automatically expires all the caches based on that template digest. The digest system does dependency tracking as well. So for example, a call to <%= render @notifications %> inside an index.html.erb file make make it such that any cache calls in that file depend on the notifications/_notification digest as well. It's a full dependency tree.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

Thanks for the explanation 👍 I've looked in the cache helper and it's fairly readable.

@kaspth
Copy link
Member Author

kaspth commented Feb 16, 2015

@georgeclaghorn I guess I was illegible 😎

@dhh you had another concern on the original issue:

The one improvement I'd like to see is that the partial being rendered should still be able to have it's own caching call that's used when the partial is referred to on it's own, but this call obviously needs to be disregarded when rendered as part of a multi_get.

What do you mean by disregarded?
Should we try and extract the inner template content from the cache call?
Or that the collection renders the template as is - so it's cached independently - but stores it under a key the collection itself generates?

If it's the last scenario, then we're doing it. As far as I can understand my own code 😅

@dhh
Copy link
Member

dhh commented Feb 16, 2015

It should do the former. This setup should work with only 1 cache:

# index.html.erb
<%= render @notifications %>

# In the following case, the render collection call detects a caching match, 
# and will only cache things ONCE. 
# notifications/_notification.html.erb
<% cache notification do %>
 Something something
<% end %>

# In the following case, the render collection call detects a caching MISMATCH, 
# and will not attempt to do any caching whatsoever.
# notifications/_notification.html.erb
<% cache notification.event do %>
 Something something
<% end %>

The partial with the cache call has to match what we expect from the render collection cache. If it does not, no caching. Because otherwise the render collection cache would make a bunch of caches that don't expire on the right things. In the example above, notification.event might change sooner than notification, but if render collection just caches on notification, it won't invalidate.

Makes sense?

@kaspth
Copy link
Member Author

kaspth commented Feb 16, 2015

Right. I think it actually does work like that.

We do check if the cache call matches the name (i.e. notification and not notification.event). So as long as we just generate the same key as cache notification then we can read from the cache.

Is that what you meant by Just Work in the original issue?

@dhh
Copy link
Member

dhh commented Feb 16, 2015

Yes, cools. Then we’re on the same page 👍

On Feb 16, 2015, at 14:01, Kasper Timm Hansen notifications@github.com wrote:

Right. I think it actually does work like that.

We do check if the cache call matches the name (i.e. notification and not notification.event). So as long as we just generate the same key as cache notification then we can read from the cache.

Is that what you meant by Just Work in the original issue?


Reply to this email directly or view it on GitHub.

@kaspth
Copy link
Member Author

kaspth commented Feb 16, 2015

@dhh phew! This cache stuff screws with your head. I'll add a test to verify this 👍

@dhh
Copy link
Member

dhh commented Feb 16, 2015

Haha, yeah, it’s a lot of balls to keep in the air at the same time! We’re deducing a lot of stuff here. Both the caching fit and the caching key that mixes in the partial digest.

On Feb 16, 2015, at 14:06, Kasper Timm Hansen notifications@github.com wrote:

@dhh phew! This cache stuff screws with your head. I'll add a test to verify this


Reply to this email directly or view it on GitHub.

@jeremy
jeremy reviewed Feb 17, 2015
View changes
actionview/lib/action_view/railtie.rb Outdated
@@ -34,6 +34,8 @@ class Railtie < Rails::Railtie # :nodoc:
ActionView::Resolver.caching = app.config.cache_classes
end
end

PartialRenderer.collection_cache = Rails.cache

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

I'd expect this to use config.action_controller.cache_store (ActionController::Base.cache_store) since that's already used for fragment caching, and falls back to Rails.cache if unconfigured.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

👍

@jeremy
jeremy reviewed Feb 17, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated
end
end

def automatic_cache_eligable?

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

s/eligable/eligible

@jeremy
jeremy reviewed Feb 17, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated
def cache_collection?
if ActionController::Base.perform_caching
@options[:cache].present? || automatic_cache_eligable?
end

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

If we aren't checking for cache: '' or cache: [] values, we can rely on truthiness alone:

def cache_collection?
  ActionController::Base.perform_caching && @options.fetch(:cache, automatic_cache_eligible?)
end

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

The original implementation would turn caching off with cache: false. Is there a better way to express that?

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

cache: false will result in @options.fetch(:cache, default_value) returning false, so cache_collection? will return false.

@jeremy
jeremy reviewed Feb 17, 2015
View changes
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb Outdated

def automatic_cache_eligable?
if collection_only_renders_one_template? && !custom_collection_cache_key?
@template.source =~ /\A<% cache\(?\s*(\w+)/ && $1 == inferred_cache_name.to_s

This comment has been minimized.

Copy link
@jeremy

jeremy Feb 17, 2015

Member

Can we push this check down into templates and memoize it to avoid repeated checks? Then template handlers can provide their own (possibly non-ERb) decision-making about hoisting partial caches up to the collection.

This comment has been minimized.

Copy link
@kaspth

kaspth Feb 17, 2015

Author Member

Good call 👍

I just found it can't be in here anyway. When a template is rendered once, it's source will be set to nil, so our check will fail.

Adding eligible_for_automatic_collection_caching? to templates shortly.

@dubek

This comment has been minimized.

I think I'm missing something: I tried to remove the <% cache customer do %> line and the corresponding <% end %> line; the tests (test/controller/caching_test.rb) still pass. I think the following line doesn't actually check whether the cache was used:

ActionView::PartialRenderer.expects(:collection_with_template).never

(used twice in the test)

BTW - my goal was to try to beat the regexp of resource_cache_call_pattern, for example: add extra spaces like <% cache or spaces around the <% cache ... do %> line (I think that erubis trims spaces if the only thing on the line is a <% ... %> directive).

This comment has been minimized.

Copy link
Owner Author

kaspth replied May 24, 2015

Thanks @dubek, you're right. I've submitted a pull request here: rails#20284.

Let us know if you find a way to beat the resource_cache_call_pattern regex 😄

huacnlee added a commit to ruby-china/homeland that referenced this pull request May 23, 2015
@dubek
Copy link
Contributor

dubek commented May 25, 2015

So I used the new tests from #20284 and then tried to tackle the resource_cache_call_pattern regex. Generally trying to parsing Ruby code with regex is probably not the correct thing to do, but not sure there is other easy approach for this task here.

The following contents for _commented_customer.html.erb break the tests (which means - they are not caught by the regex):

  1. Two comments at top:

    <%# I'm a comment %>
    <%# I'm a comment too %>
    <% cache customer do %>
      <%= customer.name %>, <%= customer.id %>
    <% end %>
    
  2. Multi-line comment at top:

    <%# I'm a multi-line
        comment %>
    <% cache customer do %>
      <%= customer.name %>, <%= customer.id %>
    <% end %>
    
  3. Spaces in front of <% cache customer do %> (they are not emitted to the output generated by Erubis):

    <%# I'm a comment %>
      <% cache customer do %>
      <%= customer.name %>, <%= customer.id %>
      <% end %>
    
  4. Extra spaces between <% and cache:

    <%# I'm a comment %>
    <%      cache customer do %>
      <%= customer.name %>, <%= customer.id %>
    <% end %>
    
  5. This is probably accidentally caught by the regex, even though it shouldn't ($1 will be _is_full):

    <% cache_is_full = true %>
    
  6. The example in the comment says that <% cache notification.event do %> should fail to a extract, but it actually matches and sets $1 to notification..

Many of these are not idiomatic Ruby style; but still it's very strange if caching works OK when you have one space in <% cache ... but stops working (without any notice) when you have two spaces there. You wouldn't expect any performance changes in Ruby programs when you change whitespace in the program.

For 1-4 above you can run erubis -x test/fixtures/customers/_commented_customer.html.erb and see that the output is an identical Ruby program (say if you parse it with Ripper or something like that).

chloerei added a commit to ruby-china/homeland that referenced this pull request May 26, 2015
…ate Rails include that: rails/rails#18948"

This reverts commit dde8d19.
@kaspth
Copy link
Member Author

kaspth commented May 26, 2015

Sweet, you've found some bugs! Rather than burden this already closed pull request, can you open a new issue or if you've beat the regex (or want to do it) can you open a pull request? 😄

We should add more tests for Action View Template's eligible_for_collection_caching?

@schneems
Copy link
Member

schneems commented Mar 9, 2016

Is this behavior mentioned in the guides?

@ghiculescu
Copy link
Contributor

ghiculescu commented Jun 20, 2016

Not sure if this should be in a new issue or if a comment here is fine. I recently came across an issue using https://github.com/n8/multi_fetch_fragments on a long list of complicated partials. It turns out the computation of digests for the partial was being repeated n times and thus was taking longer than the time saved by this technique. Specifically, calculating the digest took 13ms, and was being repeated for 300 partials, which was resulting in 4 seconds of overhead before even reading from memcached.

ghiculescu/multi_fetch_fragments@59612d5 is my fix for it. It's a pretty simplistic fix - just disable Rails' digesting if we are providing our own cache keys - but it serves my needs.

I think it would be good to add a similar sort of option to pass through to https://github.com/rails/rails/pull/18948/files#diff-738cc3c10058587e78559e48066ad0bfR54 since otherwise similar issues will come up for other people.

@kaspth does this make sense? Happy to log this somewhere else if you prefer.

edit: just came across n8/multi_fetch_fragments#31 so I'm not the only one having this issue.

@kaspth
Copy link
Member Author

kaspth commented Jul 5, 2016

@ghiculescu hey 👋

Rails 5 has got you covered. It keeps a template digest cached throughout a request in development (they are expired when the request is finished, so reloading is still possible). See #20361 and my attached pull request for more.

@ghiculescu
Copy link
Contributor

ghiculescu commented Jul 5, 2016

It's an issue in production too @kaspth - though apparently fixed in rails 5 as well #14645 (comment)

@kaspth
Copy link
Member Author

kaspth commented Jul 6, 2016

@ghiculescu prepared SQL statements are an entirely different feature altogether from template digests. Is there something I'm not getting here?

@mickey
Copy link

mickey commented Jul 30, 2016

Any idea why write_multi is not used when writing the non cached renders?

@schneems
Copy link
Member

schneems commented Aug 1, 2016

Any idea why write_multi is not used when writing the non cached renders?

I don't know. I haven't looked into it. However I could see a problem if you were creating many many cache items, say hundreds...to keep them all in memory before trying to write them all at the same time. So maybe it would be better to have that be configurable instead of a default.

Either way if you want to add it feel free to submit a PR and we can talk more there.

@mickey
Copy link

mickey commented Aug 2, 2016

@schneems Makes total sense thanks for the explanation. I'll take a look.

@kaspth
Copy link
Member Author

kaspth commented Aug 2, 2016

@mickey you mean other than there being no write_multi for ActiveSupport::Cache as far as my google skills can find? 😁

But yeah, do try to implement write_multi and make it work here!

@mickey
Copy link

mickey commented Aug 2, 2016

@kaspth Yes sorry about that. I discovered a little earlier that write_multi is a specificity of a redis backend we use.

@aried3r
Copy link
Contributor

aried3r commented Apr 7, 2017

@schneems Yes. It's here http://edgeguides.rubyonrails.org/caching_with_rails.html#collection-caching

Isn't this incomplete since 11644fd which made it automatic if the partials are set up just right?

partials are just right

@kaspth
Copy link
Member Author

kaspth commented Apr 7, 2017

Nope, check master that has the latest implementation.

@aried3r
Copy link
Contributor

aried3r commented Apr 7, 2017

Got it, found b4558c1. For some reason I was browsing your fork instead of rails/rails. ¯\_(ツ)_/¯

@kaspth
Copy link
Member Author

kaspth commented Apr 9, 2017

@aried3r that's the one, yeah. 👍

hlcfan added a commit to hlcfan/pokr that referenced this pull request May 20, 2017
In favor of rails/rails#18948
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

You can’t perform that action at this time.