Rails 4 : Custom routes, multiple edit forms for a single model - ruby

I have a Rails 4 app, with a Thing model that have 3 attributes : attr_1, attr_2, attr_3.
I would like to have two edit forms :
the first one would update attr_1 and attr_2
the second one would only update attr_3
I have written four methods in my things_controller.rb :
def edit
#edit form for attr_1 & attr_2
end
def edit_attr_3
#edit form for attr_3 only
end
def update
#update attr_1 & attr_2
end
def update_attr_3
#update attr_3 only
end
And here's my routes.db
resources :things do
member do
get :edit_attr_3
put :update_attr_3
end
end
Now, GET to both edit views work (/things/:id/edit and /things/:id/edit_attr_3) but on submit, the thing#update form is called both times (thing#update_attr_3 is never called)
How can I proceed to get thing#edit_attr_3 action linked to thing#update_attr_3 please ?
EDIT :
Answer, thanks to Ross & Steve Klein :
In my views, I am using bootstrap_form_for. Usually the :url parameter is optional, as standard edit/update are linked by convention. In the case of custom edit or update actions, it is necessary to specify the update path.
<%= bootstrap_form_for #thing, url: update_attr_3_thing_path(#thing) do |f| %>
<% end %>

It's hard to say without seeing the code for your form... but if you are using form_for then the route it will map to the update action in both cases (with an object that is not a new object).
To make it work with your unique update action, you will have to specify the url that form_for should use. Something like:
<%= form_for #thing, url: things_update_attr_3_path(#thing) do |f| %>
# other form stuff .......
You can use bundle exec rake routes at the command prompt to see what your current routes are, so that you now what route helper to use with form for.

Related

How to implement link_to, AJAX, remote: true, respond_to without rendering new url Rails 6

I'm trying to implement adding upvote and downvotes to an app, submit the controller actions remotely using a button or link_to, and refresh a count section with AJAX.
Somehow upvoting or downvoting always redirects to a path of a member. When I use head :no_content, I can't submit the form aka link_to. Having some respond_to do | f |... also just renders the action URL.
Thus, remote: true is kind of not working, as I have another controller using <%= form_for([entry, entry.review], remote: true, :authenticity_token => true) do |f| %> and it works perfectly.
I have tried implementing here, here, here, and this tutorial and this but nothing seems to work.
I'm using the acts_as_votable. Everything works except the routing functionality with AJAX
# routes.rb
resources :entries do
member do
post 'upvote'
post 'unupvote'
post 'downvote'
post 'undownvote'
end
end
# entries_controller.rb
def upvote
#entry.liked_by current_user
end
def unupvote
#entry.unliked_by current_user
end
def downvote
#entry.disliked_by current_user
end
def undownvote
#entry.undisliked_by current_user
end
# entries/index.html.erb
...
<%= button_to upvote_entry_path(entry.id), remote: true,
class: "btn",
id: "upvote-button-#{ entry.id }" do %>
<i class="bi bi-hand-thumbs-up" style="color: #ababab;" id="upvote-<%= entry.id %>"></i>
<small id="upcount-<%= entry.id %>" >
<%= render 'entries/upvote', entry: entry %>
</small>
<% end %>
...
# entries/_upvote.html.erb
<%= entry.get_upvotes.size %>
# entries/upvote.js.erb
$('#upvote-<%= entry.id %>').className('bi bi-hand-thumbs-up-fill');
$('#upvote-<%= entry.id %>').attr('href', '/entries/<%= entry.id %>/unupvote');
$('#upcount-<%= entry.id %>').html('<%=j render 'entries/upvote', entry: entry %>');
Edit, I have changed my link_to to button_to, changed the routes to post
Not sure if your a tag is inside other elements but you might want to consider using a button_to instead of a link_to.
So far the temporary solution, not probably recommended but still does the job programming practice, is hiding and unhiding certain elements on specific conditions.
Upon user interaction (e.g onClick), unhide and hide elements while updating values that resemble the controller update. Simply simulate a refresh and update the form paths/controller actions they point to.
This will mostly work better with boolean check-box forms, likes or dislikes where variables will only alternate between two instances at max. Hence you don't have to write much javascript to handle pseudo outputs.
But still, I'm sure there's a better method for example WebSockets through Action Cable. That can probably be overkill but the closest to realtime updates. respond_to just doesn't seem to work

function to change attribute using a button

I am creating a small college project in Ruby on Rails, and I came across a problem: I have a table named Person and another called Tools.
People have many tools and each tool and possessed by a person.
I need to add a feature that allows the loan of tools between people of the system. For this, I created an attribute called 'loan' in the Tools table, which has a value of 0 means that the tool not this borrowed and if it is 1, that this borrowed. In view show the tools, I created a button with the function to borrow the tool. My problem is how do to make this button change the attribute 'loan' from the Tools table from 0 to 1 and then reverse it. Someone would have a solution or a better suggestion?
I would create 2 routes name "barrow" and "retrieve" like this:
#config/routes.rb
get '/tool/:id/barrow' => 'tools#barrow', as: :barrow
get '/tool/:id/retrieve' => 'tools#retrieve', as: :retrieve
Now in your view you can do something like
# tool.index.html.erb
...
<% if tool.loan %>
<%= link_to retrieve_path(tool), 'Retrieve', class: 'btn ...' %>
<% else %>
<%= link_to barrow_path(tool), 'Barrow', class: 'btn ...' %>
<% end %>
...
now you have to create the controller action you have to create the retrieve and barrow methods
#app/controllers/tool_controller.rb
class UserController < ApplicationController
...
def retrieve
#tool = Tool.find_by(params[:tool])
#tool.update_attributes(:loan, 0)
end
def barrow
#tool = Tool.find_by(params[:tool])
#tool.update_attributes(:loan, 1)
end
...
end
I hope that this help you

Ruby on rails: What is the most simple way to show one post per page with permalink

Right now under my posts controller i have methods
def main
#post = Post.all
end
def show
#post = Post.find(params[:id])
end
I am wondering what is the most basic and simple way to show one post per page and have next and previous links to it. Here i am referencing to main.html.erb . So for instance just have localhost:3000/posts and under that page i can have next and previous links to browse through the post.
Would I be needing some kind of ajax to this? If not then how i can do this using simple activerecord and other elements of rails?
Note: After I click on next, i do need to have the permalink of the post in the url tab.
You could do something like this. Add previous and next method to your model
def previous
posts = where('id > ?', id).limit(1)
if posts.nil?
nil
else
posts.first
end
end
def next
posts = where('id < ?',id).limit(1)
if posts.nil?
nil
else
posts.first
end
end
and then in your view you could do something like this.
unless #post.next.nil? #to show the link to the next post
link_to #post.next
end
unless #post.previous.nil? #to show the link to the next post
link_to #post.previous
end
Anyhow this method isn't that optimized since you will add more two more database queries to get previous and next posts.
You could use the gem will_paginate and set the default per page to 1, as in:
class Post
self.per_page = 1
end
For more information about will_paginate:
https://github.com/mislav/will_paginate

Do calculation using radio_button variable to nested form controller - Rails 3

I have a edit form which I have a radio_button that I would like to pass to a controller action and then use it to do a calculation. In the view I have:
<div class="field">
<%= radio_button_tag(:rating_select, "Up") %>
<%= label_tag(:rating_select, "Good.") %>
<%= radio_button_tag(:rating_select, "Down")%>
<%= label_tag(:rating_select, "Bad.")%>
</div>
In the controller I have:
def rating
#post = Post.find(params[:id])
.....
end
def update
#post = Post.find(params[:id])
##rating_select = params[:rating_select]
if #post.rating_select == "Up"
#post.score += 5
elsif #post.rating_select == "Down"
#post.score -= 5
end
......
end
Currently it is ignoring the if statement so the parameter isn't getting set properly. Ideally I would like to just use a temp variable from the view to use in the if statement to decide if I need to add or subtract in the update. But I also have a rating_select field in post if I need to use it also. Thanks.
UPDATE:
Thanks. That makes sense, I changed it to below but it still isn't incrementing or decrementing the score based on the radio box. So it seems it isn't getting the rating_select?:
def update
#post = Post.find(params[:id])
if params[:rating_select]=="Up"
#post.score += 5
elsif params[:rating_select]=="Down"
#post.score -= 5
end
respond_to do |format|
....
UPDATE2:
Finally figured it out, used another model Ratings to store association. I used the before_save in the Post model and it allowed me to do the calculation and save. What a headache.
before_save :set_rating
def set_rating
if self.rating.rating_select=="Up"
rating.score += 5
elsif self.rating.rating_select=="Down"
rating.score -= 5
end
end
Well, first off, in the code you're showing the post loaded in your update action is not receiving the params from your view.
Your code for an update action should typically look like this:
def update
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
... do stuff ...
else
render :edit, :alert => 'Unable to update post.'
end
end
Second, since you're using form_tag helper and not form_for, then you're not getting the params all setup for your model (ie. nested under params[:post]). So, in this case, your rating_select option is just a value by itself, which you can test for like this:
if params[:rating_select]=="Up"
...
else
...
end
The big thing to understand from your code is #post doesn't know anything about params[:rating_select], even if you used #post.update_attributes(params[:post]), because radio_button_tag as you have it set up is not building a hash of post attributes, it's just a standalone field.
I hope that makes sense, if you don't understand please leave comments and I'll try to explain more.

Rails calling action from view

Hopefully have a simple question here but I cannot for the life of me seem to find the answer. Just started working with RoR but came from ASP MVC before. I am having an issue rendering partial views whose local variables are not necessarily tied to the variables of the main view. For instance, with a blog I am trying to render a sidebar that will link to the archive.
def sidebar
#blog_posts = Blog.all(:select => "created_at")
#post_months = #blog_posts.group_by { |m| m.created_at.beginning_of_month }
end
The partial view _sidebar is as follows:
<div class="archives">
<h4>Blog Archive</h4>
<% #post_months.sort.reverse.each do |month, posts| %>
<%= link_to "#{h month.strftime("%B %Y")}: #{posts.count}", archive_path(:timeframe => month) %>
<% end %>
</div>
The problem I am having is that if I simply do a render 'sidebar' within my main view the action does not seem to be called and #post_months is always nil. Is it possible to call the action directly from the view and simply have that render 'sidebar'? In ASP MVC I used to just make the sidebar a ChildActionOnly and Render.Action from the mainview, but in RoR I am completely clueless. Any help is appreciated!
I think what's happening here is that yout sidebar is being treated as a partial and your controller method is never being called. In that case I'd put the code currently contained in the sidebar controller method into either the ApplicationHelper module or the helper module of the current view, depending on whether or not you'd need to render the sidebar from other views.
You'd need to adapt the code a bit to work in a module. Rather than setting a session variable you should have the methods return the values you want.
Module SomeModule
def blog_posts
Blog.all :select => "created_at"
end
def post_months
blog_posts.group_by { |m| m.created_at.beginning_of_month }
end
end
Of course, that may very well need to be refactored and might not work as written, but that's the general idea I'd go with.
Good Luck.

Resources