How to use Ruby on Rails Associations? - ruby-on-rails

Description: I am following the toy_app tutorial on: https://www.railstutorial.org/book/toy_app
I am having issues with an Exercise question under section 2.3.3
Question: Edit the user show page to display the content of the user’s first micropost. (Use your technical sophistication (Box 1.1) to guess the syntax based on the other content in the file.) Confirm by visiting /users/1 that it worked.
Within my app/views/users/show.html.erb file. I tried using my #micropost.content object to display any content associated with the user.
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= #user.name %>
</p>
<p>
<strong>Email:</strong>
<%= #user.email %>
</p>
<p>
<strong>Content:</strong>
<%= (<%= #micropost.content %>) %>
</p>
<%= link_to 'Edit', edit_user_path(#user) %> |
<%= link_to 'Back', users_path %>
Result
NoMethodError in Users#show
undefined method 'content' for nil:NilClass
Conclusion
Do I need to generate a form_for for my posts for my app/view/user file? Or, because I'm setting my :content object within a private 'micropost_params' method in my microposts_controller.rb, that it will not allow object data transfer between controllers?
I tested within console the following lines of code to make sure association is working:
first_user = User.first
first_user.microposts
micropost = first_user.microposts.first
micropost.user
I feel that I need to define a method within my User controller file that gives access to my :content object within my MicroPosts Class.

Your show action should look something like the following.
def show
#user = User.find(params[:id])
#micropost = #user.microposts.first
end
Now, in the view, you can access the micropost's content using #micropost.content. What if the user has no microposts associated with him(hasn't written a micropost)? In that case, #user.microposts.first will be nil. If you're not sure what I mean, try playing with it in the rails console.
<% if #micropost %>
<%= #micropost.content %>
<% else %>
<p> User has no microposts </p>
<% end %>

Related

Update JSon parsed vote using Ruby on Rails, possible without JQuery?

I am trying to make the vote total editable from a JSon parsed API. I have the following in my rosters controller:
def index
#rosters = HTTParty.get('https:api', :headers =>{'Content_Type' => 'application/json'})
#allrosters = Roster.all
#allrostershash = {}
#allrosters.each do |roster|
image_url = roster['image_url']
#allrostershash[ image_url ] = roster
end
#rosters.each do |roster|
img_url = roster['image_url']
unless #allrostershash[img_url]
Roster.create(roster)
end
end
end
def count_vote
roster_id = params[:id]
roster = Roster.find_by(roster_id)
newvote = roster.vote + 1
if roster.update({vote: newvote})
redirect_to rosters_path
end
end
Roster is the name of my class above. In my rails views I have the following:
<% #rosters.each do |roster| %>
<div class='each'>
<%= image_tag(roster['image_url'], class: 'image') %>
<%= hidden_field_tag(roster['id']) %>
<p class='name'> <%= roster['name'] %> </p>
<p class='title'> <%= roster['title'] %> </p>
<p> <%= roster['bio'] %> </p>
<p> <b> Want to work with <%= roster['name'] %>? </b> <%= link_to image_tag('yes.jpg', class: 'yes'), rosters_path, method: :patch %>
<br>
<%= roster['vote'] %> People have said Yes! </p>
<br>
</div>
<% end %>
I would like that every time someone clicks on yes.jpg, the roster['vote'] increases by 1.
Currently my routes are set up as follows:
get 'rosters', to: 'rosters#index'
patch 'rosters', to: 'rosters#count_vote'
I'm trying to accomplish this without jquery or ajax, that's why I have the if roster.update portion to redirect to rosters_path, so it basically refreshes the page upon click. Right now it isn't updating the vote total however, I'm not sure what I'm missing. I would like to do it all on a single page so if its not possible without JQuery, any guidance in right direction is appreciated.
count_vote will silently fail if it cannot find your Roster or if the update cannot be saved. Change it so it raises an exception of anything fails.
def count_vote
roster = Roster.find(params[:id])
roster.vote += 1
roser.save!
redirect_to rosters_path
end
find will raise RecordNotFound if the Roster cannot be found. save! will raise an error if the changes cannot be saved.
These are the only params currently for some reason ActionController::Parameters {"_method"=>"patch", "authenticity_token"=>"qbORnCLNnI9P1zUZ02VEP3qJMwYOGa5sGw6KblPFj99mvjwZQj9VnDQ2e+6ZStJi3PJZ3MidSMsdoWlwOgBN9w==", "controller"=>"rosters", "action"=>"count_vote"} permitted: false> how would I add an id param? – Sohel 5 hours ago
I'm not very familiar with how views work, but I think as in this example, I believe you need to pass the roster into rosters_path.
<%= link_to image_tag('yes.jpg', class: 'yes'), rosters_path(roster), method: :patch %>
Similarly, if you want count_vote to redirect back to the roster you just changed...
redirect_to rosters_path(roster)

Ruby on Rails Tutorial (Michael Hartl) Chapter 2 Exercise 2.3.3.1 "Edit the user show page to display the content of the user’s first micropost."

Full descriptions of task sounds:
Edit the user show page to display the content of the user’s first micropost. (Use your technical sophistication (Box 1.1) to guess the syntax based on the other content in the file.) Confirm by visiting /users/1 that it worked.
My first idea was to update app/views/users/show.html.erb into
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= #user.name %>
</p>
<p>
<strong>Email:</strong>
<%= #user.email %>
</p>
<p>
<strong>Content:</strong>
<%= #micropost.content %>
</p>
<%= link_to 'Edit', edit_user_path(#user) %> |
<%= link_to 'Back', users_path %>
But seems I haven't got idea behind task? Any suggestions from what I should start from?
Biggest thanks for your responses)
You should be getting an Undefined method 'content' for nil:NilClass error. The problem is that #micropost is not defined in the controller method (action) and so is nil.
And you can't call the content method on a nil object since it doesn't respond to it. In other words, there is no instance method named content defined on NilClass.
To fix the error, define an instance variable #micropost in the show action of UsersController.
# users_controller.rb
def show
#user = User.find(params[:id])
#micropost = #user.microposts.first
end
#user.microposts.first returns user's first post.
If the user has no posts associated with them, #user.microposts.first will return nil. So, you have to check if #micropost is nil before displaying it in the view.
# users/show.html.erb
<% if #micropost %>
<p>
<strong>Content:</strong>
<%= #micropost.content %>
</p>
<% end %>
I think you might be able to just do this in the users/show.html.erb:
#user.microposts.first.content
While not elegant, it's the simplest and satisfies the "edit the user show page" requirement of the exercise.
I also added #user.id because without it you don't know which user to add the microposts to for testing. Then you need to test to see if there is a microposts in order not break the code trying to display nil.
<p>
<strong>ID:</strong>
<%= #user.id %>
</p>
<% if #user.microposts.first %>
<p>
<strong>First Post:</strong>
<%= #user.microposts.first.content %>
</p>
<% end %>

Ruby on rails cannot display form for comments before displaying comments

I have comments on my post and I can display my comments first and form for writing comments after, but cannot display my form first and comments after. I'm pretty certain that the reason begind that is the .build:
<%= form_for([#question, #question.replies.build]) do |f| %>
So inside this form you just enter the comment body and click submit.
The displayed data is: commenter (user who commented), body of the comment and created_at.
The error I get is:
undefined method `first_name' for nil:NilClass
and the extracted source is:
<%= render #question.replies%>
<% #question.replies.each do |reply| %>
<div class="reply">
<p><%= link_to reply.user.first_name, user_profile_path(reply.user) %> says:</p>
<p><%= reply.body %></p>
<p>Answered <%= time_ago_in_words(reply.created_at) %> ago</p>
<% if current_user==reply.user %>
you should have some sort of #current_user available to you right?
So try doing something like this
<%= form_for([#question, #question.replies.build.tap{|a| a.user = #current_user}]) do |f| %>
The issue is that the comment that you are building with the form_for has a nil user value.
The solution to this is that you have to check if the reply is nil before displaying anything.
<% #question.replies.each do |reply| %>
<% unless reply.user.nil? %>

undefined method `comments' for nil:NilClass - listing for a specific user

There are posts, comments & users. I want to post number of comments for a specific user in comments list on Post show page. I know I need to define #user, but I don't know how to define it, so that it shows specific number for every user. User is author of the comment.
Post controller
def show
#post = Post.find(params[:id])
#comment = Comment.new
#comments = Post.find(params[:id]).comments.order(created_at: :desc)
#user = ???
end
Post /show action - error is for - #user.comments.count
<div class="row">
<% #comments.each do |comment| %><br>
<div class="col-sm-4"> <%= link_to comment.user.name, user_path(comment.user) %> <br>
<%= image_tag(comment.user.smallimage) %><%= #user.comments.count %>
</div>
<div class="col-sm-8"> <b><%= comment.title %>:</b>
<div>
<% if comment.content.length > 250 %>
<%= truncate(comment.content, length: 250) %>
<%= link_to_function '...Read more', "$(this).parent().html('#{escape_javascript comment.content}')" %>
<% else %>
<%= comment.content %>
<% end %>
</div>
</div>
<% end %>
</div>
If you define an #user you will only have one user or a group of users that doesn't relate to your comments. You don't need it. You can just use the relationship between user and comment like this:
<%= comment.user.comments.count %>
Note that this will run a database query for each comment to get the total comment count for that user. For performance reasons you may want to use a counter cache for this instead.
If you defined the association in the post model belongs_to :user then you can probably do #user = #comment.user
But, if that's the case then you can call that from the view with #comment.user.name or something like that.
I'd like to see the whole codebase if you've pushed to GitHub.

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