Rails 4 Fragment Cache Entire Page. How to Expire Cache In Model - ruby-on-rails-4

How do I expire the main-page fragment in a model?
In my HTML
<% cache 'main-page' do %>
# html here
<% end %>
In my Post Model
after_create :clear_cache
after_update :clear_cache
def clear_cache
ActionController::Base.new.expire_fragment('main-page')
end
This doesn't clear the cache. If I create or update a post, the cache doesn't clear. If I run ActionController::Base.new.expire_fragment('main-page') in rails console it returns 'nil'. If I run Rails.cache.clear instead of ActionController::Base.new.expire_fragment('main-page') in the post model, it works.

I believe your issue is the digest, so if you change your cache to this it should work:
<% cache 'main-page', skip_digest: true do %>
# html here
<% end %>
If you want to use this kind of style, where the cache doesn't expire and relies on detecting model changes to invalidate, you may want to consider using an Observer or Sweeper, which were removed from Rails 4, but are useful for this pattern:
https://github.com/rails/rails-observers
Not the answer you are looking for perhaps, but another way:
Make the cache key based on the max updated_at from the Post model.
Whenever any post changes, the cache key will automatically miss and go retrieve the latest posts to re-cache that part of the view.
module HomeHelper
def cache_key_for_posts
count = Post.count
max_updated_at = Post.maximum(:updated_at).try(:utc).try(:to_s, :number)
"posts/all-#{count}-#{max_updated_at}"
end
end
And then in your view:
<% cache cache_key_for_posts, skip_digest: true do %>
# html here
<% end %>

Related

How do I render a page for nested routes

To learn rails, I started to write my own (stripped down) version of reddit. Currently, I have my comments routes nested inside my post routes as such:
resources :posts do
resources :comments
end
For my comments controller, under index & create I have the following
def index
#post = Post.find(params[:post_id])
#comments = #post.comments
if params[:comment].nil?
#comment = Comment.new
#comment.post_id = #post.id
else
#comment = Comment.find([:comment])
end
end
def create
#comment = Comment.new(comment_params)
#comment.user_id = current_user.id
#comment.post_id = params[:post_id]
if #comment.save
#flash
redirect_to post_path(#comment.post)
else
#flash
render 'index'
end
end
Which works well, except the last part: render
I want my comments to display on the same page as the other comments (just like reddit), so I would prefer not use the www.example.com/post/4/comment/new path, and instead do it through the www.example.com/post/4/comments path.
I understand that I can do a redirect, however I want to keep the comment text, so the user can make corrections. Is there a way to properly do this with a render, as opposed to me putting the text in a session variable and doing a redirect? I understand this is an edge case, but am trying to use this as a learning opportunity.
You can pass the comment to the index partial - no?
<%= render "index", locals: {comment: #comment} %>
Or try a partial;
<%= render partial: "index", locals: {comment: #comment} %>

url_for in an email sent by a rake task in rails 4

I have a rake task that sends out daily digest emails of player activity during a day. (See example code below.) If I run PlayerActivityMailer.activity_report.deliver in my console, everything works just fine. However, when I try to invoke the rake task, I get the following error:
rake aborted!
ActionView::Template::Error: arguments passed to url_for can't be handled.
Please require routes or provide your own implementation
After doing some research, I found that in Rails 4, they totally nerfed ActionView::Helpers::UrlHelper.url_for (http://apidock.com/rails/v4.1.8/ActionView/Helpers/UrlHelper/url_for - notice the giant red minus sign under the 4.0.2). If you look at the source, you can see the error I'm seeing - it no longer takes options. As far as I can tell, that functionality still exists in other url_fors, such as the one in ActionDispatch::Routing::UrlFor. Also, the error message suggests including Rails.application.routes.url_helpers.
What I've tried
include ActionDispatch::Routing::UrlFor in both the rake task (inside the task) and the mailer (both at the same time, and each separately)
include Rails.application.routes.url_helpers in the same places and configurations, both with and without the UrlFor include.
The error still persists. My guess is that the page view is still insisting on using the ActionView::Helpers::UrlHelper version of url_for. I don't think I can include things actually in the views (which is sloppy looking and hacky even if I could).
Example Code
(heavily sanitized)
config/environtments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost:3000' }
lib/tasks/player.rake:
namespace :player do
task :activity => :environment do
PlayerActivityMailer.activity_report.deliver
end
end
app/mailers/player_activity_mailer.rb:
class PlayerActivityMailer < ActionMailer::Base
def activity_report
#activities = PlayerActivity.all
mail(to: 'foo#bar.com', subject: 'activity report')
end
end
app/views/player_activity_mailer/activity_report.html.erb:
<% #activites.each do |activity| %>
Player: <%= link_to activity.player.name, player_url(id: activity.player.id) %>
...
<% end %>
I also have a model Player, resources :players in my routes.rb file, and a PlayerActivity class with an association to Player.
I'm currently using the (really horrifying) workaround of #base_url = Rails.configuration.action_mailer.default_url_options[:host] in my mailer action and "http://#{#base_url}/players/#{activity.player.id}" in my view instead of the player_url part.
Help!
Have you tried passing just your player in the URL? Like this:
<% #activites.each do |activity| %>
Player: <%= link_to activity.player.name, player_url(activity.player) %>
<% end %>

If else check for ujs file

I am building an Rails 4.1 webapp and I am using a an ajax form (remote true)
and a create.js.erb file with this content:
$("div.box-content").prepend('<%=j render #comment %>');
$(".empty-message").hide();
$(".textfield").val("");
How can I check if #comment is empty?
Right now it´s adding an empty row when there is no content (I don't want to add a row if comment is not set).
Update
def create
#comment = #object.comments.create(comments_params)
end
You can either check server-side (conditionally output different JavaScript) or you can check in client-side in JavaScript.
Checking server-side involves testing whether #comment is set, and simply not output any of the prepending JavaScript:
<% if #comment.present? %>
$("div.box-content").prepend('<%=j render #comment %>');
<% end %>
$(".empty-message").hide();
$(".textfield").val("");
Checking client-side involves outputting the empty value in a variable so you can inspect it in JavaScript vefore prepending it to your element:
var comment = '<%= j render #comment %>';
if (comment) {
$("div.box-content").prepend(comment);
}
$(".empty-message").hide();
$(".textfield").val("");
I would use the first approach.

dots in rails URL

I'm trying to add a show page to my devise users but can't seem to get the id value passed properly, how do I create a link to the show page? For some reason rails is doing this to URL
/show.1
users controller:
def show
#user = User.find(params[:id])
end
user/show.html.erb
<h1><%= #user.username %></h1>
link to user profile also gives me issues id no method
<%= link_to #post.email, users_show_path(#user.id) %>
routes.rb
get "users/show"
get "pages/faq"
get "pages/about"
devise_for :users
resources :posts
You have defined the show route as:
get "users/show"
Notice that there is no dynamic segment in this route, like :id. That means your route is not expecting any value to be passed. BUT while linking to this route you passed #user.id
<%= link_to #post.email, users_show_path(#user.id) %>
which the route was not expecting. So, Rails assumed that you are passing the format (like .html, .json, etc.) which is why you see the url formed as users/show.1.
To fix this, I would suggest you to add a dynamic segment to your route to capture the id of a user correctly.
Change
get "users/show"
With
get "users/show/:id" => "users#show", as: :users_show
For a user with id = 1, when you click on the user profile link the url generated would be http://yourdomain/users/show/1

Multi select file upload field with nested attributes in Rails

Currently I have a Note model which accepts nested attributes for an Attachments model, which uses Carrierwave. When adding a Note, I have a nested form to allow attaching file to the new Note:
Nested form field:
<%= f.file_field :image, multiple: true, name: "attachment[file]" %>
I am using the Cocoon gem to add the nested field. While I can easily let them add multiple file upload fields with Cocoon, and add multiple attachments that way, I only want to load one file upload field, and let them use multi select to select multiple images.
When I do this, the file upload field says '2 Images' next to it. However, upon form submission, only one file is listed under 'attachments_attributes'. I need all of the attachments to submit at once as the Note has not yet been saved.
What is the proper way to accomplish this? I am aware of the Railscast on this topic however it did not seem to address my particular scenario.
Any help is appreciated.
Just append [] to your params
<%= f.file_field :image, multiple: true, name: "attachment[file][]" %>