How to access current_user helper with Hotwire & Turbo.js? - ruby-on-rails

I’m following this tutorial to implement a feature where user can submit Trivia/Interesting Facts.
I want to restrict (edit/delete) functionality to the admin or author of each item.

I’ve created a helper class in .application_controller
def author_of?(resource)
user_signed_in? && current_user.id == resource.user_id
end
But when I'm using this in Turbo-frame with Hotwire I’m getting this error
| ActionView::Template::Error (Devise could not find the Warden::Proxy instance on your request environment.
Here's my code for reference
index.html
<%= turbo_stream_from #stadium, :trivia %>
<%= tag.div id: "#{dom_id(#stadium)}_trivia" do %>
<%= render partial: "trivia/trivium", collection: #stadium.trivia %>
<% end %>
<% end %>
_trivium.html.erb
<%= turbo_frame_tag trivium do %>
<%= trivium.body %>
<% if author_of?(trivium) || admin? %>
<%= button_to "Delete", trivium_path(trivium), class: "btn btn-small btn-danger btn-link mr-2", method: :delete, data: { confirm: "Are you sure?" } %>
<% end %>
<% end %>
How can I access the current_user helper in the comment partial to check if the current_user is the author or admin (and should be allow to delete/edit)?

The reason this errors is, as mentioned here, partials with Turbo frames are rendered without any of your global helpers available.
That SO question points to a Hotwire forum here where they discuss possible solutions. The best one in my view is described here.
The gist is:
Pass in current_user as an argument to your partial, rather than accessing it from within your partial
Do your permission check within the partial, rather than the helper
<%= render partial: "trivia/trivium", collection: #stadium.trivia, user: current_user %>
You might consider turning your author_of method into a model method as well, and then you'll have the option to use it within your partial.

Related

RoR and Ajax: How can I make my Ajax request work?

I'm tying to do an Ajax request on a delete method for active storage files so my page won't reload.
I have two controllers: 'project_steps' (i'm using wicked gem) and 'projects'.
My view: project_steps/fourth_step.html.erb
<% if #project.supporting_docs.attached? %>
<div id="remove_file">
<%= render partial: "existing_files", :locals => {project: #project} %>
</div>
<% end %>
My partial: project_steps/_existing_files.html.erb
<% #project.supporting_docs.each do |file| %>
blah blah
<%= link_to 'Remove', delete_file_attachment_project_url(file.signed_id),
method: :delete, remote: true, class: "btn btn-sm btn-danger" %>
<% end %>
My projects_controller:
def delete_file_attachment
file = ActiveStorage::Blob.find_signed(params[:id])
file.attachments.first.purge
respond_to do |format|
format.js
end
end
projects/delete_file_attachment.js.erb:
$('#remove_file').html("<%= j render(partial: 'project_steps/existing_files', :locals =>
{project: #project}) %>")
My Routes:
resources :projects do
member do
delete :delete_file_attachment
end
end
scope 'projects/:project_id' do
resources :project_steps
end
My Error
ActionView::Template::Error (undefined method `supporting_docs' for nil:NilClass):
3: <strong>You have attached the following files:</strong>
4: </div>
5: <br>
6: <% #project.supporting_docs.each do |file| %>
7: <div class="row">
8: <div class="col">
My delete works fine and I see why the error is there but i'm wondering how can I make Ajax work and what i'm doing wrong? Happy to provide as much code as needed! Ty.
P.S if anyone would like to suggest a solution other than going through partial you feel might be better by all means!
When using partials with locals, you access the variable without the #, i.e. to access the variable in project_steps/_existing_files.html.erb you need to use project instead of #project:
project_steps/_existing_files.html.erb
<% project.supporting_docs.each do |file| %>
blah blah
<%= link_to 'Remove', delete_file_attachment_project_url(file.signed_id),
method: :delete, remote: true, class: "btn btn-sm btn-danger" %>
<% end %>
Please also note that using #project in your projects/delete_file_attachment.js.erb doesn't work if you don't set the #project variable in the controller action delete_file_attachment, i.e. you'd probably need to add a line along #project = Project.find ... to your delete_file_attachment method.
See the Layouts and Rendering in Rails guide for more informations about using locals in partials.

RoR Rnder a partial cant find method

I have a classes User and Company, I want to re-use the users partial as the to render company staff.
In my CompaniesController I have:
def staff
#company=Company.find(params[:id])
#users=#company.works_fors.paginate(page: params[:page], :per_page => 10)
#title=#company.name+" staff."
end
And in my staff.html.erb template I have:
<% if #users.any? %>
<ul class="users follow">
<%= render #users %>
</ul>
<%= will_paginate %>
<% end %>
This is the works_fors/_works_for partial:
<%= render :partial => 'user' %>
Which Renders
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.developer? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
However this throws an error on the user object as it cant find the method
undefined local variable or method `user' for~~
I think this is because Im calling the user object from within companies but there is a defined relationship, or do I need to redefine in companies ?
It's hard to tell, but it appears that what you call #users in your controller is in fact not a User collection, but a WorkFor collection.
#users = #company.works_fors...
What you mean is:
#works_fors = #company.works_fors...
This means that staff.html.erb is working with a works_for collection. So you should rename the variable in your template to avoid confusion.
# staff.html.erb
<% if #works_fors.any? %>
<ul class="users follow">
<%= render #works_fors %>
</ul>
<%= will_paginate #works_fors %>
<% end %>
Now we know we are rendering a works_for partial. So an instance of works_for is be available inside the partial. We need to ask it for its associated user instance, and pass it to the render method.
# works_fors/_works_for.html.erb
<%= render works_for.user %>
As a bonus, you can save yourself some queries by preloading the users.
#works_fors = #company.works_fors.includes(:user)...

Rails creating deletion link for redis entry

Here is my issue:
I am setting up internationalization on my site (so we can have multiple translations of the text on the pages). I have followed a rails cast to set up a page that can manage the translations instead of me manually having to edit every yml file.
I have set everything up and can create entries fine, I am trying to add the ability to delete an entry and I have hit a wall. I can't seem to set up the link correctly to delete the entry from redis. The first thing that made this complicated (at least to me) is that I am not deleting an object that was created through active record (like a user etc). So instead of using the active record object to construct the url for the link_to or form_for I have to construct it manually.
From what I have read so far I have to put the link in a form (and set to post since we are modifying the redis db). So I have been trying to create the correct syntax in the form for tag to direct to the action I have set up in the controller.
Controller:
class InternationalizationTranslationsController < ApplicationController
def index
#translations = I18n.backend.store
end
def create
I18n.backend.store_translations(params[:locale], {params[:key] => params[:value]}, :escape =>false)
redirect_to internationalization_translations_url, :notice => "Added translation"
end
def destroy
puts "Key is: #{params[:key]}"
I18n.backend.delete(params[:key])
redirect_to internationalization_translations_url, :notice => "Removed translation"
end
end
View:
<%= form_tag internationalization_translations_path do %>
<p>
<%= label_tag :locale %>
<%= text_field_tag :locale %>
</p>
<p>
<%= label_tag :key %>
<%= text_field_tag :key %>
</p>
<p>
<%= label_tag :value %>
<%= text_field_tag :value %>
</p>
<p><%= submit_tag "Submit" %></p>
<% end %>
</div>
<div class="grid_7 top_padding">
<table class="trans_table">
<% #translations.keys.each_with_index do |key, i| %>
<tr class="<%= i%2 == 0 ? "even" : "odd" %>">
<td><%= key %></td>
<td><%= #translations[key] %></td>
Then I played with form_for and form_tag looking at the documentation (form helpers and form tag docs) eventually ending with these, that still do not work:
<%= form_tag(controller: "internationalization_translations", action: "destroy", method: "post", key: key) %>
<%= submit "Delete" %>
<% end %>
and now
<%= form_tag(internationalization_translations_path, action: "destroy", method: "post", key: key) do %>
<%= submit_tag "Delete" %>
<% end %>
I also played with the link_to for a while before coming across this post which linked to why the delete link/button should be in a form because it is editing the DB so it needed to be post instead of get. I am a little frustrated because this seems like a pretty straight forward task but I am running into some difficulties finding a clear answer regarding my particular issue, specifically the routing for this link for a redis entry and not an activerecord object.
**also since the form for the button is being created in a loop for each entry I should probably have the form named with an index so it is specific for each button?
Any insight or links would be greatly appreciated.
Thanks,
Alan
Ok so I ended up figuring it out resetting some things up and taking marvwhere's advice. I wanted to set it up as a link without a form, how it generated for other controllers that were manipulating active record objects. But since this was a different case making a custom action other than the default destroy action worked.
<%= form_tag(destroy_key_internationalization_translations_path, method: :post) do %>
<%= hidden_field_tag 'key', key %>
<%= submit_tag "Delete" %>
<% end %>
where I created the destroy_key action within the internationalization_translation controller.
Also the deleting of the key from redis needed to be changed. I had to use the actual Redis instance created. So instead of
I18n.backend.delete(params[:key])
in my initializer I had to set a global variable when creating the Redis instance:
TRANSLATION_STORE = Redis.new(:db => 10)
and then call the delete on that object
TRANSLATION_STORE.del(params[:key])

First argument in form cannot contain nil or be empty in rails 4

i am new to rails and doing a project with the help of Michael Hartl' tutorial of rails 4 http://ruby.railstutorial.org/chapters/following-users#sec-following_and_followers_pages
while creating a form for following users it's showing
First argument in form cannot contain nil or be empty in rails 4
<%= form_for(current_user.relationships.find_by(followed_id: #user.id)) do |f| %>
<div>
<%= f.hidden_field :followed_id %>
</div>
<%= f.submit "Follow", class:"btn btn-large" %>
<% end %>
relationships_controller.rb
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
redirect_to #user
end
Can you please give me solution to avoid this error? while i am inserting data in relationships table via rails console. it's ok. but it cant create any new object from form and showing this error.
Solution . if i change the first argument like follwing then its working but does not getting the unfollow button
<%= form_for(Relationship.new(followed_id: #user.id)) do |f| %>
You get this error because, the current_user is not following #user and there is no relationship between them, thus current_user.relationships.find_by(followed_id: #user.id) returns nil as a parameter to the form.
Refer Michael Hartl's tutorial properly, you will see
_follow_form.html.erb
<% unless current_user?(#user) %>
<div id="follow_form">
<% if current_user.following?(#user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
There are two partials to show the follow form button,
1) When the user is following another user/page there is _unfollow.html.erb partial
_unfollow.html.erb
<%= form_for(current_user.relationships.find_by(followed_id: #user),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
2) When the user is not following another user there is _follow.html.erb partial
_follow.html.erb
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
In Hartl's tutorial its not working, but found a solution, its simple, While i am checking if the cuurent_user is following user or not? if not then partial will throw me to follow page.where if create the form with
<%= form_for(Relationship.new(followed_id: #user.id))%>
i can create following users
The error message is
First argument in form cannot contain nil or be empty in rails 4.
This means that in
form_for(current_user.relationships.find_by(followed_id: #user.id))
current_user.relationships.find_by(followed_id: #user.id) returned nil. In other words, of the current user's relationships, none of them had followed_id == #user.id.
To fix this, try to figure out why you are searching for the given #user.id, and why you expect the relationship to exist.
Update
Since you are trying to create a new relationship, you can use
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
instead. find_* tries to find an existing record in the DB, whereas build makes a record in memory with the given attributes (i.e. not saved to DB yet.)

access a partial's form_for(#variable) from any page

I just finished Hartl's RoR tutorial and am now trying to mess around with some more stuff.
Specifically: I'm trying to allow the user to create microposts on any page, by rendering the micropost partial in the header.html.erb file (which is rendered on every page).
the partial:
<%= form_for(#micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "micropost" %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
Doing this has resulted in the error on line #1 of the partial: undefined method 'model_name' for NilClass:Class on any page which I have not fixed by adding #micropost = current_user.microposts.build in the controller method that links to said view. For example:
#in controllers/static_pages_controller.rb
def about
if signed_in?
#micropost = current_user.microposts.build
end
end
Would fix this error when I visit the about page
I've been trying to figure out a way to do a "blanket fix" that will work on all pages without me having to paste in the declaration everywhere, any ideas?
I think you have SessionController, is created as follow guide in Tutorial, so you can make a helper method in SessionController, example:
def post_micropost
if signed_in?
#micropost = current_user.microposts.build
end
end
Then, in your StaticsController, add a before_filter at the top of controller:
before_filter :post_micropost
So, in any action of StaticPagesController, user can post micropost also if they are signed in.
You don't need to use the form_for builder here; Rails also provides a form_tag helper for more generic forms:
<%= form_tag create_micropost_path, method: :post do %>
<%= text_area_tag :micropost_content, placeholder: "micropost" %>
<%= submit_tag "Post", class: "btn btn-large btn-primary" %>
<% end %>
This way, you don't need to build the object when loading every page, but microposts#create can still pull data from params[:micropost]. See here for more info.
You can add this before the form
<% #micropost ||= current_user.microposts.new %>

Resources