I'm following along with the rails tutorial by Michael Hartl Chapter 13 but instead of microposts, I'm creating animals.
My view of animals shows a "delete" hyperlink that is supposed to delete an animal record from my list but the view action doesn't seem to get to the point where it uses the destroy method at all.
I read through all the answers in a similar post here but did not find those answers to help in my case.
I'm currently in a development environment on AWS cloud9 as instructed in the tutorial. I really appreciate any pointers as I have been struggling with this one for days.
Here is my code from the view:
<li id="animal-<%= animal.id %>">
<%= link_to gravatar_for(animal.user, size: 50), animal.user %>
<span class="user"><%= link_to animal.user.name, animal.user %></span>
<span class="ear_tag"><%= animal.ear_tag %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(animal.created_at) %> ago.
<% if current_user?(animal.user) %>
<%= link_to "delete", animal, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
This view is called from a feed view:
<% if #feed_items.any? %>
<ol class="animals">
<%= render #feed_items %>
</ol>
<%= will_paginate #feed_items %>
<% end %>
Which comes from the home page view:
<div class="col-md-8">
<h3>Animal Feed</h3>
<%= render 'shared/feed' %>
</div>
I have reviewed the <%= link_to... line many times and it seems to be correct. My controller code is:
class AnimalsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def destroy
#animal.destroy
flash[:success] = "Animal deleted"
redirect_to request.referrer || root_url
end
def correct_user
#animal = current_user.animals.find_by(id: params[:id])
redirect_to root_url if #animals.nil?
end
end
I noticed that I never see the flash "Animal deleted" so that tells me I probably don't get to that point in the controller method.
My model code is:
class Animal < ApplicationRecord
belongs_to :user
default_scope -> {order(created_at: :desc) }
validates :user_id, presence: true
validates :ear_tag, presence: true, length: {maximum: 20}
end
Here is my application.js file from app/assets/javascripts:
//= require jquery
//= require bootstrap
//= require rails-ujs
//= require turbolinks
//= require_tree
Here is what the server log says after I click on the "delete" tag in my rendered view in the browser:
Started DELETE "/animals/308" for 23.25.133.17 at 2018-09-06 21:11:06 +0000
Processing by AnimalsController#destroy as HTML
Parameters: {"authenticity_token"=>"vwF6cWow+6B3BxOiJElVY0aQmMGr4WLWDOCxgB0C03nRLcQDKC3YCUqBr4ahVwlSKN7bEYRrmGytyI1fPgvavw==", "id"=>"308"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 102], ["LIMIT", 1]]
Animal Load (0.2ms) SELECT "animals".* FROM "animals" WHERE "animals"."user_id" = ? AND "animals"."id" = ? ORDER BY "animals"."created_at" DESC LIMIT ? [["user_id", 102], ["id", 308], ["LIMIT", 1]]
Redirected to https://cabfa13dd4f34e67b634d4f52a7a046f.vfs.cloud9.us-west-2.amazonaws.com/
Filter chain halted as :correct_user rendered or redirected
Completed 302 Found in 4ms (ActiveRecord: 0.3ms)
I also found one of my tests to fail:
FAIL["test_animal_interface", AnimalsInterfaceTest, 1.5248641059999954]
test_animal_interface#AnimalsInterfaceTest (1.53s)
"Animal.count" didn't change by -1.
Expected: 38
Actual: 39
test/integration/animals_interface_test.rb:33:in `block in <class:AnimalsInterfaceTest>'
Filter chain halted as :correct_user rendered or redirected
The problem is you have #animals(which is not defined) instead of #animal, so redirect_to root_url if #animals.nil? always succeeds which results in destroy action failing always. You should change #animals to #animal
def correct_user
#animal = current_user.animals.find_by(id: params[:id])
redirect_to root_url if #animal.nil?
end
Related
I'm new to rails, having just finished Michael Hartl's excellent Rails Tutorial. I'm building out another project app loosly following that framework, but with differences.
I'm partway in and have already run into an issue where many parts of my app seem unusually slow, and I don't see an obvious reason why. I've put details of my users index page below, because that is the slowest. As shown below, it takes 9.1s to load, of which ~8.7s is loading the views. This is on the dev environment (AWS Cloud9), but even when deployed on Heroku it takes ~5s to render the page with only 13 users in the database!
5s is pretty bad at this limited scale, and the rails tutorial sample app renders the users index page in 447ms total with 50 users shown! (running on a similar AWS EC2 instance) I don't see where my app is different enough to cause a 20x increase in load time.
Render /users server output:
Started GET "/users" for 24.85.170.222 at 2023-01-29 21:52:03 +0000
Cannot render console from 24.85.170.222! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#index as HTML
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:12:in `current_user'
Rendering layout layouts/application.html.erb
Rendering users/index.html.erb within layouts/application
User Count (1.1ms) SELECT COUNT(*) FROM "users"
↳ app/views/users/index.html.erb:5
User Load (0.6ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 30], ["OFFSET", 0]]
↳ app/views/users/index.html.erb:8
Rendered users/index.html.erb within layouts/application (Duration: 7719.3ms | Allocations: 280179)
Rendered layouts/_rails_default.html.erb (Duration: 7.6ms | Allocations: 6444)
Rendered layouts/_shim.html.erb (Duration: 0.0ms | Allocations: 9)
Company Load (0.1ms) SELECT "companies".* FROM "companies" INNER JOIN "company_users" ON "companies"."id" = "company_users"."company_id" WHERE "company_users"."user_id" = ? AND "company_users"."employee" = ? LIMIT ? [["user_id", 2], ["employee", 1], ["LIMIT", 1]]
↳ app/views/layouts/_header.html.erb:35
Rendered layouts/_header.html.erb (Duration: 963.3ms | Allocations: 1473)
Rendered layouts/_footer.html.erb (Duration: 0.2ms | Allocations: 73)
Rendered layout layouts/application.html.erb (Duration: 8693.9ms | Allocations: 289996)
Completed 200 OK in 9147ms (Views: 8697.1ms | ActiveRecord: 2.2ms | Allocations: 291862)
users_controller.rb:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :show]
before_action :correct_user, only: [:edit, :update]
def index
#users = User.paginate(page: params[:page])
end
end
/models/user.rb:
class User < ApplicationRecord
before_save :downcase_email
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
#Returns true if given token matches the digest
def authenticated?(token)
#debugger
return false if session_digest.nil?
BCrypt::Password.new(session_digest).is_password?(token)
end
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#returns a session token to prevent session hijacking
def set_session_token
#session_token = SecureRandom.urlsafe_base64
update_attribute(:session_digest, User.digest(#session_token))
return #session_token
end
private
def downcase_email
email.downcase!
end
end
sessions_helper.rb:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
#guard against session replay attacks
session[:session_token] = user.set_session_token
end
def current_user
if (user_id = session[:user_id])
#sess_user ||= User.find_by(id: user_id)
if #sess_user && #sess_user.authenticated?(session[:session_token])
#current_user = #sess_user
end
end
end
def current_user?(user)
user && user == current_user
end
def logged_in?
!current_user.nil?
end
#confirms a logged in user
def logged_in_user
unless logged_in?
store_location #store requested URL in a cookie for friendly forwarding
flash[:danger] = "Please log in"
redirect_to root_url, status: :see_other
end
end
end
/views/users/index.html.erb:
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% #users.each do |user| %>
<li>
<%= link_to user.display_name, user %>
<% if current_user.superadmin? && !current_user?(user) %>
| <%= link_to "delete", user, data: { "turbo-method": :delete, turbo_confirm: "Delete #{user.email}?" } %>
<% end %>
</li>
<% end %>
</ul>
<%= will_paginate %>
views/layouts/application.html.erb:
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="utf-8">
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
<%= javascript_importmap_tags %>
</head>
<body>
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
My views/layouts/_header.html.erb file also hits is_logged_in? and current_user a few times.
I am using all the same Gems as the railstutorial.org sample app, so a Gem shouldn't be the issue.
At first I thought that current_user was hitting the database multiple times based on the log. So I memoized it with the #sess_user ||= User.find_by(id: user_id) line. But this didn't affect load times hardly at all, and when I compared with the sample I app I realized this wasn't it as the db hits were cached: CACHE User Load (0.0ms)
I feel like there's something obvious that I'm missing. I've read up on N+1 queries and using .includes, especially as there are some other models on my site for which that might be relevant, but the fact that the issue occurs even on the very plain users index page perplexes me.
I'm still only partway through development, but I want to try and figure out this issue before I get too far ahead and things get only more complex. Would be very appreciative of any insights!
Alex's first comment on this question drove straight to the heart of the issue. I refactored current_user as shown below to memoize the return value so that is_password? is only called once per view. Instant relief - page load times are now sub-400ms.
All credit to Alex. He nailed it. His comment should really be "the answer", but I feel like I should post this here so the question shows as "answered".
def current_user
#memoize current user so authenicated? only runs once per page (its very slow # 200ms ea)
#current_user ||= begin
if (user_id = session[:user_id])
user ||= User.find_by(id: user_id)
if user && user.authenticated?(session[:session_token])
user
end
end
end
end
Sorry for the vague title.
I want to enable comments on a post / comments type system (in my case snags and snag comments).
I have a snags table, a snag_comments table and a has many / belongs to relationship between snag and snag comments.
On the show page for each snag I have a form rendering for snag comments. When I submit the form, it redirects to the root url and doesn't save anything to the snag_comments table.
This is all the information I get:
Started POST "/snag_comments" for ::1 at 2019-05-07 12:53:16 +0100
Processing by SnagCommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"yBefFfnI289UXqQYzF37DBRGQSk2Uqu41/OQO/mnET9aj2S3OqjLmSGtgyyZkTDz0aSdKzSPofQNeSzx/UH3ng==", "snag_comment"=>{"snag_id"=>"183", "content"=>"sdfgfsdgsdfgds"}, "commit"=>"Send"}
Resident Load (0.3ms) SELECT "residents".* FROM "residents" WHERE "residents"."id" = $1 ORDER BY "residents"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
Redirected to http://localhost:3000/
Filter chain halted as :redirect_residents rendered or redirected
So my params are going through. I don't know why it's not saving to the database.
In my snags controller I have:
def show
#snag_attachments = #snag.snag_attachments.all
#snag_comment = SnagComment.new
#snag_comments = SnagComment.where(snag_id: #snag.id)
end
And my snag comments controller (which is currently incomplete while I figure out this broken part):
class SnagCommentsController < ApplicationController
skip_authorization_check
before_action :set_parent
def create
#snag_comment = SnagComment.new(snag_comment_params)
if #snag_comment.save
redirect_to snags_path
end
end
private
def set_parent
#parent = #snag
end
def snag_comment_params
params.require(:snag_comment).permit(:content, :image, :snag_id,
:commenter, :first_name, :last_name)
end
end
The form which is rendered (views/snag_comments/_form):
<div class="snag-comment-form">
<%= simple_form_for snag_comment, url: snag_comments_path do |f| %>
<% if snag_comment.errors.any? %>
<div class="submission-errors">
<ul>
<% snag_comment.errors.full_messages.each do |error_msg| %>
<li>
<%= fa_icon "exclamation-circle" %>
<%= error_msg %>
</li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :snag_id, value: #snag.id %>
<%= f.input :content, label: false, as: :text, placeholder: t(".content"), maxlength: 1000, required: true, class: "snag-comment-content" %>
<div class="snag-comment-submit">
<%= f.submit t(".send"), class: "btn branded-btn send" %>
</div>
<% end %>
</div>
And where the form is called from (views/homeowners/snags/show):
<%= render 'snag_comments/form', snag_comment: #snag_comment %>
I need the snag comments to be outside of the homeowner module because it also needs to be accessible to the admin module (admin and homeowner can comment on the same snags).
I can't figure out why the form is redirecting and not saving. I have tried removing any redirect and just specifying that the form should be saved but it still redirects to root url and doesn't save, so something must be going wrote between passing the params and actually saving.
You seem to have missed the most basic fundamental on how to setup a nested resource - nesting the route:
resources :snags do
resources :snag_comments
end
This will make the "parent-child" relation part of the RESTful route and make it very clear what is going on VS implicitly passing that information in the form body.
class SnagCommentsController < ApplicationController
# ...
before_action :set_snag
# POST /snags/:snag_id/snag_comments
def create
#snag_comment = #snag.comments.new(snag_comment_params)
if #snag_comment.save
redirect_to #snag, notice: 'Comment created'
else
render :new
end
end
private
def set_snag
#snag = Snag.find(params[:snag_id])
end
def snag_comment_params
params.require(:snag_comment)
.permit(:content, :image,:commenter, :first_name, :last_name)
end
end
To create a form that posts to the nested route use an array:
def show
#snag_attachments = #snag.snag_attachments
#snag_comment = #snag.comments.new
#snag_comments = #snag.comments
end
<%= simple_form_for([#snag, #snag_comment]) do |f| %>
# ...
<% end %>
Make sure you remove the input for the parent ID. It is explicitly passed as segment in the URL.
I am a beginner of Ruby on rails, I failed when I try to delete the object that I create. here is the code.
controller
class PuzzlesController < ApplicationController
def index
#puzzles = Puzzle.all
end
def show
#puzzle=Puzzle.find(params[:id])
end
def new
#puzzle = Puzzle.new
end
def create
#render plain: params[:puzzle].inspect
#puzzle = Puzzle.new(puzzle_params)
if(#puzzle.save)
redirect_to #puzzle
else
render 'new'
end
end
def edit
#puzzle=Puzzle.find(params[:id])
end
def update
#puzzle=Puzzle.find(params[:id])
if(#puzzle.update(puzzle_params))
redirect_to #puzzle
else
render 'edit'
end
end
def destroy
#puzzle = Puzzle.find(params[:id])
#puzzle.destroy
redirect_to puzzle_path
end
private def puzzle_params
params.require(:puzzle).permit(:title,:body,:category,:difficulty)
end
end
show
<h2><%= #puzzle.title %></h2>
<p><%= #puzzle.body %></p>
<p><%= #puzzle.category %></p>
<p><%= #puzzle.difficulty %></p>
<hr>
<%= link_to "Edit", edit_puzzle_path(#puzzle), :class => 'btn btn-default'%>
<%= link_to "Delete", puzzle_path(#puzzle),
method: :delete,
data: {confrim: 'are you sure? '},
:class => 'btn btn-danger'%>
when i click on delete, its like re-render the page. i searched a lot on internet and cant find a solution for it.
this is the information on rails server.
Started GET "/puzzles/1" for ::1 at 2017-04-02 18:51:18 +0930
Processing by PuzzlesController#show as HTML
Parameters: {"id"=>"1"}
Puzzle Load (0.5ms) SELECT "puzzles".* FROM "puzzles" WHERE "puzzles"."id" = ? LIM IT ? [["id", 1], ["LIMIT", 1]]
Rendering puzzles/show.html.erb within layouts/application
Rendered puzzles/show.html.erb within layouts/application (1.0ms)
Completed 200 OK in 63ms (Views: 46.2ms | ActiveRecord: 0.5ms)
There are several issues in your code:
1) There is a typo in the link_to parameter list (confirm, instead of confrim):
<%= link_to 'Delete', puzzle_path(#puzzle),
method: :delete,
data: { confirm: 'are you sure? '},
class: 'btn btn-danger' %>
2) At the end of your destroy method you cannot redirect to a singular puzzle path, because you just deleted the puzzle. Redirect to the index page instead:
redirect_to puzzles_path
3) But most important. Links with method: 'delete' only work with JavaScript and Rails 5.0 depends on jQuery. Because you wrote that the links just redirects to the show page, I guess you do not have included the Rails JavaScript files into your asset pipeline:
# Add this to the head of your `views/layout/application.rb`:
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
# Ensure that your `assets/javascripts/application.js` include the following lines:
//= require jquery
//= require jquery_ujs
Ensure in your browser's concole that the files are loaded without an error.
change your redirect path
redirect_to puzzles_path
In my RoR application, I have an update_multiple method that updates multiple records with the user's inputs. However, for some reason I get the error ActiveModel::ForbiddenAttributesError despite using strong params. Can someone please help me fix this?
The update_multiple method in Recipients_Controller is as follows:
def update_multiple
#email = Email.find_by_id(params[:email_id])
if Recipient.update(params[:recipient].keys, params[:recipient].values)
#listofcontacts = Recipient.where("id in (?)", params[:recipient].keys)
#account = Account.find_by_id(#email.account_id)
#listofcontacts.each do |f|
recipient_message = #email.message
recipient_message = recipient_message.gsub("VAR1", f.var1)
contact = Contact.find_by_id(f.contact_id)
#unsubscribe = Rails.application.message_verifier(:unsubscribe).generate(contact.id)
UserEmails.send_email(#email, #account, contact.email, #unsubscribe, recipient_message).deliver_now
end
flash[:notice] = "recipients were updated"
redirect_to root_path
else
render 'edit_multiple'
end
end
private
def recipient_params
params.require(:recipient).permit(:contact_id, :group_id, :email_id, :var1, :var2, :var3)
end
This method takes the user input from this form:
<%= form_for :recipient, :url => update_multiple_recipients_path, :html => { :method => :put } do %>
<fieldset>
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered" id="example">
<thead>
<tr>
<th>Contact</th>
<% if #email_message.upcase.include? "VAR1" %><th>VAR1</th><% end %>
</tr>
</thead>
<tbody>
<%= hidden_field_tag :email_id, #email %>
<% #recipients.each do |recipient| %>
<tr class="odd gradeX">
<%= fields_for "recipient[]", recipient do |recipient_fields| %>
<td><%= recipient_fields.label recipient.contact.firstname %> <%= recipient_fields.label recipient.contact.surname %></td>
<% if #email_message.upcase.include? "VAR1" %><td><%= recipient_fields.text_field :var1, :required => true, :maxlength => 20 %></td><% end %>
<% end %>
</tr>
<% end %>
</tbody>
</table></br>
<%= submit_tag 'Send Email', {:class => 'btn btn-primary'} %></br>
<%= link_to "Back", edit_email_path(#email) %>
</fieldset>
<% end %>
The development.log reads this:
Started PUT "/recipients/update_multiple" for ::1 at 2017-03-03 09:33:10 +0000
Processing by RecipientsController#update_multiple as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"BJtQ56CW169tJ0Yqlc7BZNZk8SiTCauvkpNkXRUqVv4WESSS/DGFVDe3uQnfTxxDgif8lbg8THtmxHT9bOh0zw==", "email_id"=>"292", "recipient"=>{"635"=>{"var1"=>"ben"}}, "commit"=>"Send Email"}
[1m[36mEmail Load (0.0ms)[0m [1mSELECT "emails".* FROM "emails" WHERE "emails"."id" = ? LIMIT 1[0m [["id", 292]]
[1m[35mRecipient Load (1.0ms)[0m SELECT "recipients".* FROM "recipients" WHERE "recipients"."id" = ? LIMIT 1 [["id", 635]]
[1m[36m (0.0ms)[0m [1mbegin transaction[0m
[1m[35m (0.0ms)[0m rollback transaction
Completed 500 Internal Server Error in 5ms (ActiveRecord: 1.0ms)
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/recipients_controller.rb:15:in `update_multiple'
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (4.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.2.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (1459.1ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/_markup.html.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/console.js.erb within layouts/javascript (1449.1ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/main.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/web-console-2.3.0/lib/web_console/templates/index.html.erb (3305.2ms)
Where it says app/controllers/recipients_controller.rb:15:in 'update_multiple', it is pointing to the line if Recipient.update(params[:recipient].keys, params[:recipient].values)
I really cannot figure out why I am getting this error. Can someone please help me?
I have looked at various other SO questions and they seem to have been solved by strong_params, but mine already has strong_params declared and isn't working.
The problems is this line:
if Recipient.update(params[:recipient].keys, params[:recipient].values)
You are passing params directly to the update method. You need to pass recipient_params into update:
if Recipient.update(recipient_params.keys, recipient_params.values)
Update
However, from your logs, it is apparent that params[:recipient] is returning a hash containing id/attribute pairs, not a single set of attributes. So you'll need to permit the attributes for each recipient id passed in params. I think this code should do it:
private
def recipient_params
params.require(:recipient).permit(permit_for_each_recipient)
end
def permit_for_each_recipient
params[:recipient].keys.inject({}){|h,k| h[k] = attributes_to_permit; h}
end
def attributes_to_permit
[:contact_id, :group_id, :email_id, :var1, :var2, :var3]
end
EDITING ANSWER AFTER COMMENTS
First of all, some good docs To help you to understand better how to use strong parameters.
Now, let's try to refactor a bit your code. Remember, methods larger than 4 lines are more risky to hide mistakes, avoid them if you can!
This is your code, I changed nothing except I moved some chunks of code into sub-methods
def update_multiple
#this cannot work because there is no instance to update
if Recipient.update(params[:recipient].keys, params[:recipient].values)
send_unsuscribe_emails
flash[:notice] = "recipients were updated"
redirect_to root_path
else
render 'edit_multiple'
end
end
private
def recipient_params
params.require(:recipient).permit(:contact_id, :group_id, :email_id, :var1, :var2, :var3)
end
def send_unsuscribe_emails
#email = Email.find_by_id(params[:email_id])
#this cannot work because params[:recipient].keys does not return a list of ids (probably you want womethink like recipients_params[:contact_id])
#listofcontacts = Recipient.where("id in (?)", params[:recipient].keys)
#account = Account.find_by_id(#email.account_id)
#listofcontacts.each do |f|
send_unsuscribe_email(f)
end
end
def send_unsuscribe_email(f)
recipient_message = #email.message.gsub("VAR1", f.var1)
contact = Contact.find_by_id(f.contact_id)
#unsubscribe = Rails.application.message_verifier(:unsubscribe).generate(contact.id)
UserEmails.send_email(#email, #account, contact.email, #unsubscribe, recipient_message).deliver_now
end
and now the solution could be like this
def update_multiple
#listofcontacts = Recipient.where("id in (?)", recipients_params[:contact_id])
if #listofcontacts.update(recipient_params)
send_unsuscribe_emails
flash[:notice] = "recipients were updated"
redirect_to root_path
else
render 'edit_multiple'
end
end
private
def recipient_params
params.require(:recipient).permit(:contact_id, :group_id, :email_id, :var1, :var2, :var3)
end
def send_unsuscribe_emails
#email = Email.find_by_id(params[:email_id])
#account = Account.find_by_id(#email.account_id)
#listofcontacts.each do |f|
send_unsuscribe_email(f)
end
end
def send_unsuscribe_email(f)
recipient_message = #email.message.gsub("VAR1", f.var1)
contact = Contact.find_by_id(f.contact_id)
#unsubscribe = Rails.application.message_verifier(:unsubscribe).generate(contact.id)
UserEmails.send_email(#email, #account, contact.email, #unsubscribe, recipient_message).deliver_now
end
Of course I cannot test it so, probably it will crash somewhere, but more or less this is the idea.
Hi I've set set of an appreciation controller that handles user's liking different posts. Each post has a like button and when I click it looks like the request goes through but the page doesn't refresh and update the button.
When I click like this is the log, it shows the unlike partial being returned but nothing changes:
Started POST "/appreciations" for 127.0.0.1 at 2011-04-22 05:47:28 -0700
Processing by AppreciationsController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zQQJeXZiAPFeQ/7AEy9hvQac01+jq929XUXHrd6eSOE=", "appreciation"=>{"liked_id"=>"3"}, "commit"=>"Like"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE ("users"."id" = 4) LIMIT 1
Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 3) ORDER BY posts.created_at DESC LIMIT 1
SQL (0.5ms) INSERT INTO "appreciations" ("created_at", "liked_id", "liker_id", "updated_at") VALUES ('2011-04-22 12:47:28.642264', 3, 4, '2011-04-22 12:47:28.642264')
Redirected to http://localhost:3000/posts/3
Completed 302 Found in 185ms
Started GET "/posts/3" for 127.0.0.1 at 2011-04-22 05:47:28 -0700
Processing by PostsController#show as HTML
Parameters: {"id"=>"3"}
Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 3) ORDER BY posts.created_at DESC LIMIT 1
User Load (1.2ms) SELECT "users".* FROM "users" WHERE ("users"."id" = 4) LIMIT 1
Appreciation Load (0.3ms) SELECT "appreciations".* FROM "appreciations" WHERE ("appreciations".liker_id = 4) AND ("appreciations"."liked_id" = 3) LIMIT 1
CACHE (0.0ms) SELECT "appreciations".* FROM "appreciations" WHERE ("appreciations".liker_id = 4) AND ("appreciations"."liked_id" = 3) LIMIT 1
Rendered posts/_unlike.html.erb (49.4ms)
Rendered users/_like_form.html.erb (77.7ms)
Rendered posts/show.html.erb within layouts/application (208.9ms)
Completed 200 OK in 248ms (Views: 212.4ms | ActiveRecord: 5.2m
appreciations controller
class AppreciationsController < ApplicationController
before_filter :authenticate_user!
def create
#post = Post.find(params[:appreciation][:liked_id])
current_user.like!(#post)
redirect_to #post
end
def destroy
#post = Appreciation.find(params[:id]).liked
current_user.unlike!(#post)
redirect_to #post
end
end
_like_form.html.erb
<% unless current_user?(#user) %>
<div id="like_form">
<% if current_user.likes?(#post) %>
<%= render 'posts/unlike' %>
<% else %>
<%= render 'posts/like' %>
<% end %>
</div>
<% end %>
_like.html.erb
<%= form_for(current_user.appreciations.
build(:liked_id => #post.id),
:remote => true) do |f| %>
<div><%= f.hidden_field :liked_id %></div>
<div class="actions"><%= f.submit "Like" %></div>
<% end %>
_unlike.html.erb
<%= form_for(current_user.appreciations.find_by_liked_id(#post),
:html => { :method => :delete },
:remote => true) do |f| %>
<div class="actions"><%= f.submit "Unlike" %></div>
<% end %>
Should the result of POST "/appreciations" be a redirect to http://localhost:3000/posts/3? You're using the :remote => true option of the form so I believe you need to change your controller to this:
respond_to do |format|
format.html { redirect_to #post}
format.js
end
And create a *.js.erb partial that does something like this:
<% unless current_user?(#user) %>
<% if current_user.likes?(#post) %>
$("#post_form").html("<%= escape_javascript(render('posts/unlike'))%>");>
<% else %>
$("#post_form").html("<%= escape_javascript(render('posts/like'))%>");
<% end %>
<% end %>
Basically I believe you're seeing no change because you're making an AJAX POST, but not doing anything with the result. You would need JavaScript that updates the innerHTML of a DOM element with the result of your post.
This post might be helpful: http://www.stjhimy.com/posts/7-creating-a-100-ajax-crud-using-rails-3-and-unobtrusive-javascript