access a partial's form_for(#variable) from any page - ruby-on-rails

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 %>

Related

How to access current_user helper with Hotwire & Turbo.js?

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.

My Rails button is not updating the database

I created a button where users can input stuff in a field and then press the button to update the database (put request) which can be seen here in show.html.erb:
<% provide(:title, #user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
<br>
<%= #user.email %>
<% if #errors %>
<p>THE FORM COULD NOT BE SAVED </p>
<ul id='errors'>
<% #errors.each do |error| %>
<li><%= error %></li>
<% end %>
</ul>
<% end %>
<br>
<% if is_admin? %>
<% if !#user.admin %>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#user) do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", :action => "set_wistia_project_ID", :method => :patch, :form_class => "form-control" %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</h1>
</section>
</aside>
</div>
The function is in user_controller.rb:
# Sets wistia_project_ID.
def set_wistia_project_ID
#user = User.find(params[:id])
#user.set_project_id
unless #user.valid?
#errors = #user.errors.full_messages
render :show
end
end
That function calls another function, just to separate things more clearly. This other function lives in user.rb:
# Sets the wistia_project_ID.
def set_project_id!(val)
self.wistia_project_ID = val # self is necessary here
save # or self.save, but the self is unnecessary here
end
My routes.rb:
.
.
.
resources :users do
member do
patch 'set_wistia_project_ID'
end
end
My problem is that right now, when you press the button, it says: Completed 500 Internal Server Error in 26ms (ActiveRecord: 0.7ms)
and
NoMethodError (undefined method `set_project_id' for #<User:0x000055b1a0914ab8>
2019-06-26T14:46:34.940086+00:00 app[web.1]: Did you mean? wistia_project_id):
Zavitoski got it right. I suggest, however, that you're doing a number of things more fundamentally wrong. Given that you're early in your rails journey, I hope you don't mind if I point a few things out.
First, and to be nit-picky, yes, you created a button. But, it is not a button "where users can input stuff in a field and then press the button to update the database". You created a button on a form. And you created a field on that form. The user can input stuff into the field. And when clicked, the button submits the form which includes the information in the field.
Now, on that form, you did:
<%= form_for(#user) do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", :action => "set_wistia_project_ID", :method => :patch, :form_class => "form-control" %>
<% end %>
There are a few things wrong with:
:action => "set_wistia_project_ID"
First, set_wisteria_project_ID is not a very ruby-ish action name. set_wistia_project_id would be more like it. Also, you're using old-form key-value formatting. And, you can use a symbol instead of a string for your action name so your code is prettier. Something, perhaps, like:
<%= f.submit "Save", action: :set_wistia_project_id, method: :patch, form_class: "form-control" %>
But, that's a mistake, too. Because you don't need a set_wistia_project_id action. (It's an action or a method, not a function.) You already have the update action. And form_for is smart enough to submit to this action if #user is an instance of User. So, really, you should do:
<%= form_for #user do |f| %>
<%= f.label :wistia_project_id %>
<%= f.text_field :wistia_project_id, class: 'form-control' %>
<%= f.submit "Save", form_class: "form-control" %>
<% end %>
I'm not sure what form_class is, but I'll trust that it's correct.
Now, in your UsersController, just do:
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
if user.update(user_params)
# do something successful
else
# do something unsuccessful
end
end
private
def user_params
# NOTE: You'll probably want to permit other stuff here, too.
params.require(:user).permit(:wistia_project_id)
end
end
Get rid of this:
class User < ApplicationRecord
# Sets the wistia_project_ID.
def set_project_id!(val)
self.wistia_project_ID = val # self is necessary here
save # or self.save, but the self is unnecessary here
end
end
Because you're just duplicating the update method. And, you probably want that attribute to be wistia_project_id, not wistia_project_ID. (Again, you never see _ID as the suffix in rails core and you might as well be conventional.) And, if you make sure you have your association set up correctly, ActiveRecord should make sure that wistia_project_id is actually a valid value.
And write your routes.rb like this:
resources :users
Because you don't need all that set_wistia_project_id business.
It appears that you are not calling the function by the name you defined, neither passing the parameter (project_id) needed.
def set_wistia_project_ID
#user = User.find(params[:id])
#user.set_project_id!(params[:wistia_project_id])
unless #user.valid?
#errors = #user.errors.full_messages
render :show
end
end
This should use the function you created and pass the parameter from the form.

Rails: Forms_For new post calling different controller on submit

I've been working on a social site using the Rails framework. I just finished setting up a form and was able to submit a couple subposts. It worked for a bit, but now when I submit a subpost using forms_for(#subpost), it attempts to submit a completely different form on a separate view. No clue why it would call a separate form that hasn't even been rendered but hoping someone can help.
SubPost Controller
class SubPostsController < ApplicationController
def create
#subpost = SubPost.new(sub_post_params)
if #subpost.save
Form I want to submit
<%= form_for(#subpost) do |z| %>
<%= render 'shared/error_messages', object: z.object %>
<div class="field">
<!--<input type="text" name="sub_post[user_id]" value="<%# current_user %>" style="display:none;"/>-->
<input type="text" name="sub_post[micropost_id]" value="<%= micropost_id %>" style="display:none;"/>
<%= z.text_area :content, placeholder: "What's on your mind?" %>
</div>
<%= z.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
Updated to answer question
So basically I have my users personal page which displays their feed displaying a form under each post to submit a subpost. On the actual show page, there is no partial or render for post form, but that is what gets called each time I submit to the subpost form. The controller for the users page has two variables that I use to render the posts and subposts, #post = #user.posts.build and #subpost = #post.sub_posts.build
You can do something like this:
#some_controller.rb
def some_method
#posts = Post.all
SubPost.new
end
In your form you can use url option to take form parameters to create action. Assuming your routes are nested you can do something like this:
<% #posts.each do |post| %>
<%= form_for :subpost, url: some_path(#user, post) do |z| %>
// you need to replace some_path(#user,post) by your path helper
<%= render 'shared/error_messages', object: z.object %>
<div class="field">
<%= z.hidden_field :post_id, value: post.id %>
<%= z.text_area :content, placeholder: "What's on your mind?" %>
</div>
<%= z.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>

NoMethodError in FrontPages#home when I try to add a form

I am pretty sure it's a very noob mistake, but I don't understand why I can't add my form directly to my homepage without causing a NoMethodError.
This is my current setup, and it works perfectly fine.
view/front_pages/home.html.erb (my home page)
<!--declare :title to be SHOULD I GET THIS-->
<% provide(:title, "SHOULD I GET THIS") %>
<div class = "container">
<h1>Should I really spend money on this?</h1>
<h2>Let's crunch in some numbers and find out...</h2>
<%= link_to "Get Started", calculate_path, class: "btn btn-info " %>
</div>
view/users/new.html.erb (where my form is right now)
<% provide(:title, "Calculate")%>
<div class = "form-group container">
<%= simple_form_for #user do |form|%>
<%= form.error_notification%>
<%= form.input :price%>
<%= form.input :wallet%>
<%= form.button :submit, "Submit", class: "submit"%>
<%end%>
</div>
controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
end
However, I want the form to be directly on my home page so the users don't need to click an extra button to get to the form. I tried
view/front_pages/home.html.erb
<!--declare :title to be SHOULD I GET THIS-->
<% provide(:title, "SHOULD I GET THIS") %>
<div class = "container">
<h1>Should I really spend money on this?</h1>
<h2>Let's crunch in some numbers and find out...</h2>
<%= link_to "Get Started", calculate_path, class: "btn btn-info " %>
</div>
<div class = "form-group container">
<%= simple_form_for #user do |form|%>
<%= form.error_notification%>
<%= form.input :price%>
<%= form.input :wallet%>
<%= form.button :submit, "Submit", class: "submit"%>
<%end%>
</div>
and it returns me the NoMethodError. I thought it's because I did not initiate a #user variable in the front_page controller, so I tried
class FrontPagesController < ApplicationController
def new
#user = User.new
end
def home
end
end
but it still does not work. I am thinking it's some concept about MVC that I still am not quite grasping. What is wrong with my code and what should I keep in mind next time so I don't make the same mistake?
PS: I use simple_form gem to generate my form
Edit: The error message is undefined method `model_name' for NilClass:Class
Error is quite simple:
Undefined method `model_name' for NilClass:Class
This error means you're trying to call the model_name method on a variable which is not populated with any data. You are not calling this method; form_for is - meaning you basically need to have #user declared in your controller, as you rightly pointed out:
class FrontPagesController < ApplicationController
def new
#user = User.new
end
def home
end
end
--
#user
The problem is that you're using the home action - you're declaring your #user variable in the new action. This means it won't be set, as it won't be called.
You'll be best doing this:
class FrontPagesController < ApplicationController
def home
#user = User.new
end
end
This will make #user available in your home action, which should resolve the error for you!
You need specify the action for the form
<%= form_for :user, url: { action: create } do |f| %>
or you can use your path helper if you have a resource defined for user
<%= form_for :user, url: user_path do |f| %>

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.)

Resources