How to set up acts_as_follower - ruby-on-rails

I'm using the gem acts_as_follower in a rails app. I set it up and it works (In console), however I'm clueless as to how to set it up in a view.
I want to make a button correspond to the user.follow and user.stop_following methods.
The github doesn't explain this. Help please.

You can create controller actions that you link to. For example in an app I have the following two actions added to a user controller. Once the routes are also setup I use the url helpers to link to the actions from my view, and end up displaying the flash messages via javascript callbacks.
UsersController:
def follow
#user = User.find(params[:id])
if current_user
if current_user == #user
flash[:error] = "You cannot follow yourself."
else
current_user.follow(#user)
RecommenderMailer.new_follower(#user).deliver if #user.notify_new_follower
flash[:notice] = "You are now following #{#user.monniker}."
end
else
flash[:error] = "You must <a href='/users/sign_in'>login</a> to follow #{#user.monniker}.".html_safe
end
end
def unfollow
#user = User.find(params[:id])
if current_user
current_user.stop_following(#user)
flash[:notice] = "You are no longer following #{#user.monniker}."
else
flash[:error] = "You must <a href='/users/sign_in'>login</a> to unfollow #{#user.monniker}.".html_safe
end
end
config/route.rb:
resources :users do
member do
get :follow
get :unfollow
end
end
Then in your view you can use the url helper to link to the controller action:
<%= link_to "Unfollow", unfollow_user_path(#user) %>

Related

How to I redirect back to my topics/show page?

Hey all so in my code I am just redirecting back to the index of all the topics and theoretically I would like to redirect back to the page.
this is my controller for this page, right now I am just using topics_path as a stand in.
class LikesController < ApplicationController
def index
end
def create
#bookmark = Bookmark.find(params[:bookmark_id])
like = current_user.likes.build(bookmark: #bookmark)
if like.save
flash[:notice] = "Successfully liked bookmark."
else
flash.now[:alert] = 'Error in liking bookmark. Please try again.'
end
redirect_to topics_path
end
def destroy
#bookmark = Bookmark.find(params[:bookmark_id])
like = current_user.likes.find(params[:id])
# Get the bookmark from the params
# Find the current user's like with the ID in the params
if like.destroy
flash[:notice] = "Successfully unliked bookmark."
else
flash.now[:alert] = 'Error in unliking bookmark. Please try again.'
end
redirect_to topics_path
end
end
this is the line from rake routes that I was to redirect_to
bookmarks_show GET /bookmarks/show(.:format) bookmarks#show
If you wish to redirect back to a specific topic's page... then you'll need to pass the topic_id through as a param so you can use it in the redirection.
Add it into the form/link you're using eg:
(note: totally making this up, obviously your code will be different)
<% form_for #like do |f| %>
<%= f.hidden_field :topic_id, #topic.id %>
Then in your create action, you just redirect using that eg:
def create
#bookmark = Bookmark.find(params[:bookmark_id])
like = current_user.likes.build(bookmark: #bookmark)
if like.save
flash[:notice] = "Successfully liked bookmark."
else
flash.now[:alert] = 'Error in liking bookmark. Please try again.'
end
redirect_to topic_path(:id => params[:topic_id])
end
Note: if you want to use some other page (eg the bookmark page) then use that instead... this is a "general howto" not a "use this code exactly as you see it here" :)

Rails app with Mongoid not saving on update

I'm making a Rails app that uses Mongoid and devise. Currently, the functionality's pretty basic, but it's not working like I expect it to. In particular, update isn't working at all.
I can create a user, but when I go back to update it, it doesn't raise any errors, but also doesn't save. This may be clearer with some code. I've got this in routes:
devise_for :users
resources :users
And this in the controller:
## WORKS PERFECTLY FOR SIGNING USERS UP, AND FLASHES CORRECTLY
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save!
flash[:success] = "Welcome!"
sign_in #user
redirect_to #user
else
render 'new'
end
end
## DOES NOT UPDATE THE USER RECORD, AND DOES NOT FLASH SUCCESS
## IT DOES, HOWEVER, REDIRECT TO SHOW, INSTEAD OF RENDERING EDIT
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.save!
flash[:success] = "Profile edited successfully!"
redirect_to #user
else
render 'edit'
end
end
So yeah. As the code suggests, going through my views to edit an existing user does NOT edit that user, nor does it give the flash saying the users was edited, but it DOES redirect correctly. I currently have NO user validations (though devise might have some) -- but changing all devise-relevant fields doesn't get around the issue, so I don't think it's a silent validation fail.
The form is of the basic
<%= form_for #user do |f| %>
<%= f.label: name %>
<%= f.text_field :name %>
...
<%= f.submit %>
<% end %>
sort.
Not sure what other code could be helpful here. Please let me know. I'm super stumped. Thanks!
you have to pass params you can use update_attributes or update_attributes!
#user = User.find(params[:id])
if #user.update_attributes!(params[:user])
...

Template is missing in action

I wrote a "follow" method in UsersController
def start_following
#user = current_user
#user_to_follow = User.find(params[:id])
unless #user_to_follow == #user
#follow_link = #user.follow_link.create(:follow_you_id => #user_to_follow.id, :user_id => #user.id)
#user.save
flash[:start_following] = "You started following" + #user_to_follow.name
else
flash[:cant_follow] = "You cannot follow yourself"
end
end
Pretty simple. And In the view, I have
<%= link_to 'Follow', follow_user_path(#user) %>
In routes,
resources :users do
member do
get 'follow' => "users#start_following", :as => 'follow'
When I click on the link, it complains: Missing template users/start_following
So, how do I make it just stay on the same page after the action?
The view page that I want to stay on is Show view of the user is to be followed.
ex: users/{user_id}. Is simply redirecting not a solution? I thought adding redirect_to {somewhere} would get rid of the error, but it didn't.
I would redirect to the user in question. If you are using the standard resourceful routes then you can just do
redirect_to(#user_to_follow)
As an aside it's generally considered bad practice to have GET requests that make changes - people normally use put/patch/post/delete requests for those. You can fall afoul of browsers pre-fetching links without the user actually clicking on them.
try:
redirect_to :back, :notice => "successfully followed someone..."
Yes redirect_to solves your problem, I suspect you forgot to add it to both branches of the unless
The code would look like this:
def start_following
#user = current_user
#user_to_follow = User.find(params[:id])
unless #user_to_follow == #user
#follow_link = #user.follow_link.create(:follow_you_id => #user_to_follow.id, :user_id => #user.id)
#user.save
flash[:start_following] = "You started following" + #user_to_follow.name
else
flash[:cant_follow] = "You cannot follow yourself"
end
redirect_to #user_to_follow
end

Rails 3 -- After Failed Sign In, Errors Do No Populate

Edit: Hmm this is interested. I just noticed, my signup route is /signup. But, once I submit the information and the form returns invalid, I'm in the route /users .
I'm building a simple app to learn rails, and I've learned to set up an authentication system.
Today, I added a new plans table, to make different subscriptions for users. The Plan model has_many users, and users belong to plans. After implementing this, I see that if I enter invalid information, error messages do not show up in the view anymore.
I have the following code in the application.html.erb file for it show up -- >
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
It works fine on other notices, but it isn't showing the error messages.
One thing to also note, is that if I enter an email incorrectly for example, Rails shows me that it was the problem by highlighting it in red(edited CSS previously to do that). But, the error messages themeselves are nowhere to be found :P
Here's my Users controller :
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:show]
before_filter :correct_user, only: [:show]
def show
#user = User.find(params[:id])
end
def new
plan = Plan.find(params[:plan_id])
#user = plan.users.build
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def index
if current_user
redirect_to(user_path(current_user))
else
redirect_to(root_path)
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to login_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
end
I did some research, and people have been saying it might because of redirects, but I'm not sure If I'm doing that.
When first visiting the signup though, it's in the form of /signup?plan_id=1, to populate a hidden field with the plan_id in the signup form. When it shows the error screen, the plan_id is no longer there, which I assumed is okay since it already POSTed it. Does that have anything to do with it?
I think the problem is that the error messages you're expecting to see are errors on the User object, not stored in the flash. Try this in your view:
<% #user.errors.full_messages.each do |error_message| %>
<div class="alert"><%= error_message %></div>
<% end %>
See the docs for ActiveModel::Errors for more info.

Rails: Keeping user spoofing checks DRY

In a fit of unoriginality, I'm writing a blog application using Ruby on Rails. My PostsController contains some code that ensures that the logged in user can only edit or delete their own posts.
I tried factoring this code out into a private method with a single argument for the flash message to display, but when I did this and tested it by editing another author's post, I got an ActionController::DoubleRenderError - "Can only render or redirect once per action".
How can I keep these checks DRY? The obvious approach is to use a before filter but the destroy method needs to display a different flash.
Here's the relevant controller code:
before_filter :find_post_by_slug!, :only => [:edit, :show]
def edit
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot edit another author’s posts."
redirect_to root_path and return
end
...
end
def update
#post = Post.find(params[:id])
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot edit another author’s posts."
redirect_to root_path and return
end
...
end
def destroy
#post = Post.find_by_slug(params[:slug])
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot delete another author’s posts."
redirect_to root_path and return
end
...
end
private
def find_post_by_slug!
slug = params[:slug]
#post = Post.find_by_slug(slug) if slug
raise ActiveRecord::RecordNotFound if #post.nil?
end
The before filter approach is still an ok option. You can gain access to which action was requested using the controller's action_name method.
before_filter :check_authorization
...
protected
def check_authorization
#post = Post.find_by_slug(params[:slug])
if #post.user != current_user
flash[:notice] = (action_name == "destroy") ?
"You cannot delete another author’s posts." :
"You cannot edit another author’s posts."
redirect_to root_path and return false
end
end
Sorry for that ternary operator in the middle there. :) Naturally you can do whatever logic you like.
You can also use a method if you like, and avoid the double render by explicitly returning if it fails. The key here is to return so that you don't double render.
def destroy
#post = Post.find_by_slug(params[:slug])
return unless authorized_to('delete')
...
end
protected
def authorized_to(mess_with)
if #post.user != current_user
flash[:notice] = "You cannot #{mess_with} another author’s posts."
redirect_to root_path and return false
end
return true
end
You could simplify it more (in my opinion) by splitting out the different parts of behavior (authorization, handling bad authorization) like this:
def destroy
#post = Post.find_by_slug(params[:slug])
punt("You cannot mess with another author's post") and return unless author_of(#post)
...
end
protected
def author_of(post)
post.user == current_user
end
def punt(message)
flash[:notice] = message
redirect_to root_path
end
Personally, I prefer to offload all of this routine work to a plugin. My personal favorite authorization plugin is Authorization. I've used it with great success for the last several years.
That would refactor your controller to use variations on:
permit "author of :post"
The simple answer is to change the message to something that fits both: "You cannot mess with another author's posts."
If you don't like the ugly* return in that last solution, you can use an around filter and conditionally yield only if the user is authorized.
around_filter :check_authorization, :only => [:destroy, :update]
private
def check_authorization
#post = Post.find_by_slug(params[:slug])
if #post.user == current_user
yield
else
flash[:notice] = case action_name
when "destroy"
"You cannot delete another author's posts."
when "update"
"You cannot edit another author's posts."
end
redirect_to root_path
end
end
*-- that's my preference, though code-wise it's perfectly valid. I just find that style-wise, it tends to not fit.
I also should add I haven't tested this and am not 100% certain it would work, though it should be easy enough to try.

Resources