Ruby on rails, PATCH method reverted by transaction rollback - ruby-on-rails

I'm not good at ruby but I have to make a project using rails and I encountered a problem. I'm making a forum application where a user can have multiple profiles and change between them. After many hours I thought that maybe it would be easier if I added a whole column to the Users table called current_profile_id where I'd store the current chosen profile by the user. I am using the PATCH method to insert a profile id into the table but upon doing that the transaction gets rolled back and I am left with nothing. I am a total newbie and kinda don't want to do this so my code is most likely terrible.
view:
<% #user.profiles.each do |profile| %>
<%= link_to content_tag(:div, profile.name), users_profiles_path(:current_profile_id => profile.id), method: :patch %>
<% end %>
<div>
<%= current_user.current_profile_id %>
</div>
user controller:
def setProfile
#user = User.find_by(id: params[:id])
#user.current_profile_id = params[:current_profile_id]
#user.save
end
route:
patch '/users/:id/profiles', to: 'users#setProfile'
screenshot from wsl teminal
I know the idea isn't really thought through but at this point I kind of have to roll with it.

The problem isn't with your patch request per se: it's that you don't have a view template set up for the controller to forward to. Try creating view template to go with your setProfile action, or redirect the request to another location from that action.

Related

What is my build method within my form_for helper accomplishing?

I'm just starting to learn Rails and I'm currently working on a watered down Facebook clone. I'd like to know what exactly my build method that I'm including in my form_for helper is doing. This is taken from The Ruby On Rails Tutorial by Michael Hartl where we did something similar, but with Twitter followers instead of friend requests. I couldn't wrap my head around it then, and I'm still having trouble now.
Here is the form that I'm using when a user sends a friend request to another user. This is a form partial rendered from #users (hence the user.id)
<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :friend_id, user.id %></div>
<%= f.submit "Send Friend Request" %>
<% end %>
This passes to my Friendships controller...
def create
#user = User.find(params[:friend_id])
current_user.send_friend_request(#user)
redirect_to current_user
end
Which makes a call to send_friend_reqeust...
def send_friend_request(other_user)
friend_requests << other_user
end
My User model has_many :friend_requests, through: :active_relationships
It works fine, but I started questioning what current_user.active_relationships.build in my form_for helper was even doing. To my (very) untrained eye, it looks like the form is passing a hidden value of friend_id which the Friendships controller then uses to find the User database record associated with that ID.
If all that my form is doing is submitting a hidden User ID, then what is the point of the build method? I know it must do something since removing it breaks my program.
Any help is appreciated. Thank you!
It is an intermediary model between User and FriendRequest models.
By using method BUILD you create a new ActiveRelationship and through that then you can create new FriendRequest.
User >> ActiveRelationship >> FriendRequest

Better way of getting objects id from another controller

I have ideas controller and static_pages controller. The latter has home action which displays all ideas and which i also use as root path.
I want the user be able to Edit the displayed ideas. So far i have this:
<% if #ideas.empty? %>
<p>Share your ideas! See what people think about it.</p>
<% else %>
<% #ideas.each do |idea| %>
<div class="panel panel-default">
<div class="panel-heading"><%= idea.name %></div>
<div class="panel-body"><%= idea.description %> <br>
<%= link_to 'Edit', edit_idea_path(idea.id) %>
</div>
</div>
<% end %>
<% end %>
I had an issue with an empty idea id which i solved by adding idea.id inside edit_idea_path
Now my question is, is that the proper, Rails way of doing it? In what other way can i fetch the idea object from this index page and use it in my ideas controller instead of static_pages controller?
I tried playing around with routing, but I have very vague understanding of it despite reading the guides and others code. I'd appreciate any insight about this matter.
First you need to understand that the requirement of your project defines what you should do in the code, whithout of concerning about the proper way to do something. You just need to follow the rails conventions.
http://guides.rubyonrails.org/active_record_basics.html#convention-over-configuration-in-active-record
Now, back to your question. You just need to create an action (that will handle a view) in your ideas_controller that will manage the edition of the data sended by de static_pages_controller, i will call it (just for example) edit_static_ideas and receive the data with params:
In your ideas_controller : app/controllers/ideas_controller.rb
def edit_static_ideas
#idea = Idea.find(params[:id])
end
Then you need to create the view in your views->ideas folder. An name it, just to continue my example i'll name it edit_static_idea.html.erb. And set the load of the data you get in #idea as a form or a form_for. Then you can submit that edited data and upload it into other action.
Then you have to configure your routes file and add
config/routes.rb
get 'edit_static_idea/:id', to: 'ideas#edit_static_idea', as: 'edit_ideas'
After that, if you run "rake routes" in your console (inside your rails project), you should see your new route (yay!)
Now you have to take the path in your route and use it in you static_pages_controller's view to redirect it to the edit_idea's view handle it by ideas_controller. And be sure that you also send the id of the selected item.
app/views/static_pages/home.html.erb:
<%= link_to 'Edit Idea', insert_your_edit_idea_obtainedinrakeroutes_path(id: idea.id) %>
At last, you only need to configure the form in your edit_static_idea.html.erb and assign it an upload/save route and redirect it to the view that you want.
for example:
In your routes file: config/routes.rb
patch 'save_edited_idea', to: 'ideas#save_edited_idea', as: 'save_edited_idea'
In your ideas_controller: app/controllers/ideas_controller.rb
def save_edited_idea
#idea = Idea.find(params[:id])
respond_to do |format|
if #idea.update(idea_params)
format.html { redirect_to the_view_that_you_want_path(id: #idea.id), notice: 'Data saved without problems.' }
else
flash.now[:alert] = "error"
format.html { render :offer }
end
end
end
I didn't wanted to be so detailed, because i wanted to help you to understand what you have to do. I hope it helps :P

How to hide parts of the view given a user role on Rails 4

I'm trying to hide parts of my views depending on the User role.
So let's say I want only admins to be able to destroy Products. Besides the code in the controller for preventing regular users from destroying records, I would do the following in the view:
<% if current_user.admin? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
The previous code works, but it's prone to errors of omission, which may cause regular users to see links to actions they are not allowed to execute.
Also, if I decide later on that a new role (e.g. "moderator") can delete Products, I would have to find the views that display a delete link and add the logic allowing moderators to see it.
And if there are many models that can be deleted only by admin users (e.g. Promotion, User) maitenance of all the ifs would be pretty challenging.
Is there a better way of doing it? Maybe using helpers, or something similar? I'm looking for something maybe like this:
<%= destroy_link 'Delete', product %> # Only admins can see it
<%= edit_link 'Edit', promotion %> # Again, only admins see this link
<%= show_link 'Show', comment %> # Everyone sees this one
I found these two questions that are similar to mine, but none of them answered my question:
Show and hide based on user role in rails
Ruby on Rails (3) hiding parts of the view
I strongly recommend pundit.
It allows you to create "policies" for each model. For your Product model you might have a ProductPolicy that looks something like this
class ProductPolicy < ApplicationPolicy
def delete?
user.admin?
end
end
In your view you can do something like this
<% if policy(#post).delete? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
If later on you want to add a moderator role, just modify the policy method
class ProductPolicy < ApplicationPolicy
def delete?
user.admin? || user.moderator?
end
end
So I kind of figured a way to move the IFs out of the view. First, I override the link_to helper in my application_helper.rb:
def link_to(text, path, options={})
super(text, path, options) unless options[:admin] and !current_user.admin?
end
Then on my views I use it as:
<%= link_to 'Edit Product', product, admin: true, ... %>
This prevents regular users from seeing admin links, but for other html tags with content inside, such as divs, tables etc., an if would still be needed.
CanCan is another gem that lets you define "Abilities" per user role.
In views you can use something like if can? :delete, #post to check if the
user may delete that specific post.
Using the CanCan and Role gems, what is still needed is a way to Check The Route and see if "current_user" has permissions to access that Route based on their role(s) - then show/hide based on that.
This saves the user clicking on things and getting told they cannot see it - or us having to write per-item "if" logic specifying what roles can see what list-items (which the customer will change periodically, as roles are changed/refined) around every single link in one's menu (consider a bootstrap menu with 50+ items nested in groups with html formatting, etc), which is insane.
If we must put if-logic around each menu-item, let's use the exact same logic for every item by checking the role/permissions we already defined in the Ability file.
But in our menu-list, we have route-helpers - not "controller/method" info, so how to test the user's ability to hit the controller-action specified for the "path" in each link?
To get the controller and method (action) of a path (my examples use the 'users_path' route-helper) ...
Rails.application.routes.recognize_path(app.users_path)
=> {:controller=>"users", :action=>"index"}
Get just the controller-name
Rails.application.routes.recognize_path(app.users_path)[:controller]
=> "users"
Ability uses the Model for its breakdown, so convert from controller name to it's model (assuming default naming used) ...
Rails.application.routes.recognize_path(app.users_path)[:controller].classify
=> "User"
Get just the action-name
Rails.application.routes.recognize_path(app.users_path)[:action]
=> "index"
And since the "can?" method needs a Symbol for the action, and Constant for the model, for each menu-item we get this:
path_hash = Rails.application.routes.recognize_path(app.users_path)
model = path_hash[:controller].classify.constantize
action = path_hash[:action].to_sym
Then use our existing Abilty system to check if the current_user can access it, we have to pass the action as a symbol and the Model as a constant, so ...
<% if can? action model %>
<%= link_to "Users List", users_path %>
<% end %>
Now we can change who can see this resource and link from the Ability file, without ever messing with the menu, again. But to make this a bit cleaner, I extracted out the lookup for each menu-item with this in the app-controller:
def get_path_parts(path)
path_hash = Rails.application.routes.recognize_path(path)
model_name = path_hash[:controller].classify.constantize
action_name = path_hash[:action].to_sym
return [model_name, action_name]
end
helper_method :get_path_parts
... so I could do this in the view (I took out all the html-formatting from the links for simplicity, here):
<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
<%= link_to "Users Listing", users_path %>
<% end %>
... and to make this not take all day typing these per-menu-item if-wraps, I used regex find/replace with capture and wildcards to wrap this around every list-item in the menu-item listing in one pass.
It's far from ideal, and I could do a lot more to make it much better, but I don't have spare-time to write the rest of this missing-piece of the Role/CanCan system. I hope this part helps someone out.

Holding off on create action

I want to have a form that anyone can fill out, but must be logged in to submit it - if not, put the process on hold until they do.
My thought process is if someone comes across the form, it checks if they are logged in, if not the user can still fill it out, but instead prompted to log in or sign up as opposed to "Submit". If they click on either link (log in/sign up) I want it to save the form data, most likely to the session, have them log in/sign up, and then have the computer check to see if there is a saved form, and if so display a confirmation like screen, where the newly registered user can accept it.
I've posted come code below that I thought would work, but it isn't - and I was thinking that knowing rails, theres probably some convention out the to do this much faster and easier!
And I don't believe the way that I am passing the form data around is correct, so if anyone can correct me there that would be great!
View
<%= form_for :comment, :url => {:action => 'create'} %>
form fields here
<% if current_user %>
<%= f.submit "Submit" %>
<% else %>
<%= link_to "Log In", save_to_session_and_log_in_path(:comment => :comment) %> or
<%= link_to "Sign Up", save_to_session_and_sign_up_path(:comment => :comment) %>
<% end %>
Controller
def save_to_session_and_log_in_path
session[:temp_comment] = Comment.new(params[:comment])
redirect_to log_in_path
end
def save_to_session_and_sign_up_path
session[:temp_comment] = Comment.new(params[:comment])
redirect_to sign_up_path
end
User* Controller
def create
#Normal create action, under the redirect:
if session[:temp_comment]
redirect_to confirm_comment
else
redirect_to users_home_page
end
end
The error I am receiving is:
ActiveRecord::RecordNotFound in CommentsController#show
Couldn't find Comment with id=save_to_session_and_log_in
Rails.root: scrubbed
Application Trace | Framework Trace | Full Trace
app/controllers/comments_controller.rb:87:in `show'
Request
Parameters:
{"comment"=>"comment"
"id"=>"save_to_session_and_log_in"}
I've tried tweaking it every which way but it still keeps getting me here so I am unable to test if any of my code is working
What are your CommentsController#create and #show action? About your error, could you paste the rake routes result for the save_to_session_and_log_in_path and the save_to_session_and_sign_up_path methods? Since you don't have an id at that moment, you should set them as collection routes.
--
The way I'd do it, though, is submitting the form to different controllers that will handle each scenario. That way, your CommentsController will be for logged in users and you can have a TemporaryCommentsController that will take care of comments made by guests.
--
Also, depending on the comment's field, I don't know if it's a great idea to store them in the session. You could probably store a tmp_comment_id in the session and recover that from the DB. Also, delete all records with a cron-job. (This is a problem only when "the comment is big and 'hard' to serialize" though).

How do I make an edit users page work on my home page?

Complete rails novice and something just isn't clicking.
On my home page /home - I use devise, so I have a check .
<% if user_signed_in? %>
<%= render "getting_started" %>
<% else %>
Welcome!
<%= link_to "Sign up", new_user_registration_path%>
<% end %>
At the moment getting_started.html.erb has some instructions and then I'm displaying /users/_getting_started_form.html.erb.
The form is there with some extra fields already catered for in the model (eg. username, which I'd like users to set once they've logged in with email/password, rendered with <%= form_for(#user) do |f| %>
.. and it renders without error once I put:
#user = :current_user
..into the home method of PagesController. But that's not right is it? I'd want to be posting to the users controller. I want the getting started form to take the data entered, post it to the users controller, and save the data to the model.
Sorry for the confusion, things just aren't making sense with the amount of things rails automates - part of the issue for me is in PHP I'd just post a form to the appropriate controller myself, and also devise - which I plunged in to get user authentication done properly quickly doesn't create a users controller for me, which I find that very confusing that sign up, etc. works without a file named UsersController being there. I'm (clearly) very confused on this and best approaches in rails. - Help very much appreciated.
Why :current_user? That's a symbol and doesn't point to any User object. Perhaps what you're after is simply current_user?

Resources