There are 2 forms on one page. I want an if else statement in the controller to use different params and variable values depending on which form was submitted. From doing a google search the best I came across was to have a value on the submit button.
<%= f.submit "Save" :value => "x" %>
If this is a plausible way I cant find how to make an if else statement for checking if the submit value is 'x'.
Something like
if submit.value == 'x'
do something
else
do something else
end
Really not sure. If there is another way of having an if else statement in the controller to catch witch form was submitted by using an id or name or whatever I'm happy to hear it.
The value of the submit button can be accessed with the help of params[:commit], so you can use it to check which form is submitted.
if params[:commit] == 'x'
do something
else
do something else
end
#Pavan has the direct answer, however if you're evaluating form submissions by their respective submit values, you've got a major issue with your pattern.
Form
Forms should be a way to pass values to your controller, which will then populate the model. You should not have to determine actions based on those values, unless you have different functionality...
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= form_for post do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
<% end %>
The above will create multiple forms, all submitting to the posts#update method:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
#post = Post.find params[:id]
#post.update post_params
end
private
def post_params
params.require(:post).permit(:x, :y, :z)
end
end
The values inside those forms don't matter, nor to which post object they were sent; they will all be evaluated in exactly the same way.
--
Actions
The other way around this is to make separate actions for the different forms:
#config/routes.rb
resources :posts do
patch :update_2, on: :member
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update_2
#post = Post.find params[:id]
#post.param = "value"
#post.update post_params
end
end
#app/views/posts/show.html.erb
<%= form_for #post, url: posts_update_2_path(#post) do |f| %>
<%= f.submit %>
<% end %>
You could use something like
<%=f.submit "Basic update", name: "basic-update" %>
<%=f.submit "Security update", name: "security-update" %>
and then check in your controller:
if params.has_key? "security-update"
#do something
elsif params.has_key? "basic-update"
#do another thing
end
Related
Lets start off with i am new to programming in rails and trying to learn by building a project. I am creating a project that has following and follower capability similar to twitter... I have implemented the option to delete a post. However, it seems that i can delete other people post as well that i am following etc. How can i implement the delete of my own post and have other user have the ability to edit modify and delete their own post.
post.rb
class Post < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 } #
default_scope -> { order(created_at: :desc) } # newest tweets / posts first
end
post controller
def destroy
#status_update = Post.find(params[:id])
if #status_update.present?
#status_update.destroy
end
redirect_to root_url
end
home
<%= link_to('Delete', post_path(#p), :method => :delete,data: { confirm: "Are you sure?" } ) %>
i was also looking at something like this:
def owned_post
unless current_user == #post.user
flash[:alert] = "That post doesn't belong to you!"
redirect_to root_path
end
end
Lets say you have a Post model and views all set up:
In your views/posts/show you can set up something like this:
<% if #post.user.id == current_user.id %>
<%= link_to "Edit", edit_post_path(#post), class:"btn btn-success btn-sm" %>
<% end %>
You will still have a small issue, users can still access the form to edit, so now in your views/posts/edit it renders a form so put a condition on it:
<% if user_signed_in? %>
<% if #post.user.id == current_user.id %>
<%= render 'form', tutorial: #post %>
<% end %>
<% else %>
<h1>stop trying to edit others post</h1>
<% end %>
Good question, though there isn't one single answer I can give you. The question of "authorization" of actions in your app is a big one, and there are gems like Pundit that you could look into for a full-fledged solution.
However, it's always good to start with the basics on your own and build up to a bigger solution. What you have already isn't wrong -- just add before_action :owned_post, only: [:delete] (perhaps rename to ensure_post_owner or such) to your controller to enforce your rule.
Another option is to scope your ActiveRecord queries to the set of objects your current user is allowed to operate on. So instead of #post = Post.find(params[:id]), you could do, #post = current_user.posts.find(params[:id]). If the user tries to modify a post they don't own, they'll get back a 404 as if that post simply doesn't exist. This can be a good strategy to avoid letting attackers enumerate which posts are and aren't in your database.
On view specify something like this:
<% if user_signed_in? && current_user.id == #post.user_id %>
# something like edit. links... delete links..
<% end %>
or you can also use gem like: cancancan
I have checkout using braintree api located at orders/new. However, the price that I want to charge the user is determined based on what the post id is. The user is linked to orders/new from the posts/show if that helps at all.
thanks in advance!
Accessing the :post_id in the create method is a little trickier because there is no link to a create view to send the :post_id with.
orders_controller create method
def create
post=Post.find(params[:post_id])
nonce = params[:payment_method_nonce]
render action: :new and return unless nonce
result = Braintree::Transaction.sale(
amount: post.price,
payment_method_nonce: nonce
)
end
end
orders new view that sends user to the create method
<h2>Purchase Ticket (refresh if fields don't load)</h2>
<p>the price is <%= number_to_currency(#post.price) %></p>
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<%= render 'payment/form' %>
When using this updated top section of my form
<%= form_tag orders_path, method: 'post' do %>
<%= hidden_field :post_id, #post.id %>
<div id="dropin"></div>
<input type="submit" value="Pay">
<% end %>
<%= #params %>
You can add the post_id into the link on the post#show view that points to the orders#new action:
<%= link_to 'Purchase', new_order_path(post_id: #post.id) %>
In your orders controller you will also need to allow the post_id to be accepted by the strong params section:
def post_params
params.permit(:post_id)
end
I do not know what is already in you orders_params method so I have given you the minimum that should work from the guides.
Then in the OrdersController action you can grab the post_id from the params:
def new
post_id = post_params[:post_id]
# ...
end
UPDATE: expanding answer to encompass additional create action criteria.
Maybe the most straight forward option is to add a hidden field in the form to store the post_id:
# app/views/payments/_form.html.erb
<%= form_tag orders_path, method: 'post' do %>
<%= hidden_field_tag :post_id, #post.id %>
<div id="dropin"></div>
<input type="submit" value="Pay">
<% end %>
This will mean that the post_id you put into the link_to method argument on the post will be stored in the form and be submitted to the create action, and you can access it in the same way:
class OrdersController < ApplicationController
def new
#post = Post.find(post_params[:post_id])
end
def create
post = Post.find(post_params[:post_id])
render text: post.price # to demonstrate
end
private
def post_params
params.permit(:post_id)
end
end
I have put together a quick demo application to show the working code.
So I have this books database and a burrows database. In burrows, there is a field for book_id and also a field for user_id, so that I can see who burrowed which book.
Now, I am trying to create a controller and view for it but it is not going well really. Right now the view is looking like this:
<% provide(:title, "Burrow") %>
<b align="center">Choose the name of the book you want t burrow'</b>
<%= form_for(#book) do |f| %>
<div class="forms">
<%= f.name %>
<%= f.check_box(:book_id) %>
<%= f.submit 'Submit!' %>
</div>
<% end %>
But this puts me to the problem where it creates an error because I want to put all books into #books in burrows controller. But I dont really see any other way? \
The final idea would be so that I have all the books displayed and after them a checkbox, so I can select which books I want to burrow. And after that I also want a dropdown menu where all users are listed, I can choose to burrow the book for another user, but the default value would be the logged in user but theres time till that. Right now I am struggline to understand, why my solution for listing books does not work?
Listing my controller here also:
class BurrowsController < ApplicationController
before_action :signed_in_user, only: [:index,:edit,:update, :destroy]
before_action :admin_user, only: :destroy
def index
#burrows = Burrow.all
end
def show
#burrow = Burrow.find(params[:id])
end
def new
#burrow = Burrow.new
end
def create
#burrow = Burrow.new(burrow_params)
if #burrow.save
flash[:success] = "Burrowing a book was successful!"
redirect_to #burrow
else
render 'new'
end
end
def listing
#book_list = Book.all
end
# Private section, makes the page unable to be seen for non logged in users
private
def burrow_params
params.require(:burrow).permit(:user_id, :book_id)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
# Redirecting not logged in user etc.
def signed_in_user
unless signed_in?
store_location
redirect_to '/sessions/new', notice: "Please sign in!"
end
end
end
Right now I am struggline to understand, why my solution for listing books does not work?
I'm not sure what listing you mean. The view you pasted apparently corresponds to the controller action Burrows#new?
So I'm going to tell you what's wrong with your view:
<%= form_for(#book) do |f| %>
This prints a form for a Book, not a Burrow. You could create a new book with this form, but that's certainly not what you're trying to do here.
You'll want to have all these variables in your controller:
def new
#users = User.all
#books = Book.all
#burrow = Burrow.new
end
so you can use collection_select with #users, have a #burrow object to use with form_for, and do a each loop on #books, for instance, like this:
<%= form_for(#burrow) do |f| %>
<% #books.each do |book| %>
<%= f.check_box :book_ids, multiple: true, book.id, nil %>
<% end %>
<%= f.collection_select :user_id, #users, :id, :name, {}, selected: current_user.id %>
<%= f.submit %>
<% end %>
Click the links for documentation on these commands: collection_select, check_box
This is not ideal code, but it's as close to your example as I can get.
I understand if you don't get this right away. Your code is a bit of a mess right now and there's too much wrong with it to be explained in one post and fixed by just one line. You might want to start over, and make a single controller action work before you try to make everything at once.
Sometimes it's better to sit back and really think about what you're trying to achieve, and first lay out how to achieve it inside your head; and only then start programming!
It seems to me that you're using the RailsTutorial by Michael Hartl, so all I can recommend to you right now is, read what you've read again more thoroughly and try to stick to his examples first until you feel confident, before really starting to make your very own application.
My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
I have the following single table inheritance classes in a rails app:
class Document < ActiveRecord::Base
validates :title, :presence => true
end
class LegalDocument < Document
end
class MarketingDocument < Document
end
I want to use the same controller and set of views to edit both LegalDocuments and MarketingDocuments, so I am using DocumentsController < ApplicationController with the following edit and update actions:
def edit
#document = Document.find(params[:id])
end
def update
#document = Document.find(params[:id])
if #document.update_attributes(params[:document])
redirect_to documents_path, :notice => "#{t(:Document)} was successfully updated."
else
render :action => "edit"
end
end
and the following in my edit view:
<%= form_for #document.becomes(Document) do |f| %>
<% if f.object.errors.present? %>
<div class="error_message">
<h4><%= pluralize(f.object.errors.count, 'error') %> occurred</h4>
</div>
<% end %>
<div>
<%= f.label :title %>
<%= f.text_field :title, :class => "inputText" %>
</div>
<%= f.submit %>
<% end %>
If title is filled in, the documents update correctly.
If title is left blank, I am returned to the edit view BUT the error is not shown.
From debugging, I know it's not showing because f.object.errors is nil. However, from debugging, I also know #document.errors is NOT nil, as expected.
My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
Yes, I noticed that too.
Just change f.object.errors.present? by #document.errors.any? ( or #document.errors.present?).
If you really want to use f.object.errors.present?, write becomes in the controller (both edit and update actions) instead of in the view:
def edit
#document = Document.find(params[:id]).becomes(Document)
end
def update
#document = Document.find(params[:id]).becomes(Document)
# ....
end
And then in the view:
<%= form_for #document do |f| %>
<% if f.object.errors.present? %>
<p>Errrorsss....</p>
<% end %>
#.....
It happens because the url of the form is build according to #document.becomes(Document) (=> PUT document/:id) but #document is created according to its "true" class (a subclass of Document).
If you have pry (highly recommended), write:
def update
#document = Document.find(params[:id])
binding.pry
# ...
end
And then inspect #document. You will see that #document is an instance of LegalDocument or the other subclass even though you called #document.becomes(Document) in your form.
So in final f.object and #document are not the same.
This explains why you can't see f.object.errors when validation fails.
Edit
The 'best way' to deal with STI and form is NOT to use becomes:
<= form_for #document, url: { controller: 'documents', action: 'update' }, as: :document do |f| %>
<% if #document.errors.any? %>
# or if f.object.errors.any?
# handle validation errors
<% end %>
# your form...
<% end %>
This enables you:
to have only one controller (documents_controller)
to have only one resource (resources :documents)
it keeps trace of your subclasses: a LegalDocument will be store as a LegalDocument. No conversion: You don't have to store its class before the conversion to Document and then reassign it later.
Plus, your subclass is available in your form, so you can (let's imagine) build a select for the type.
your controller looks cleaner: #document = Document.find params[:id] nothing more. Just like a classic resource.
If you want to share this form across different actions(typically edit and new):
<%= form_for #document, url: { controller: 'media_files', action: action }, as: :media_file do |f| %>%>
# edit.html.erb
<%= render 'form', action: 'update' %>
# new.html.erb
<%= render 'form', action: 'create' %>
Pretty much it is a bug and it should work as you initially expected. The following patch to address the issue looks like it was pulled back in October
https://github.com/lazyatom/rails/commit/73cb0f98289923c8fa0287bf1cc8857664078d43
I have a model that I want to be commentable. I am having difficulty creating a form on my model's 'show' view that will allow comments to be created. I am not finding any good or useful examples. Can anyone point me to or show me an example of how to do this?
Example:
A simple blog application. I have a model called Post. It is commentable. So on the 'show' view I want to show a Post and, at the bottom, have the fields that, when completed and submitted, create a new comment associated with the post and put it in the database.
Sounds straightforward and I have it working so I can display comments that I have seeded. I just can't get a form to work to put new ones in. Any help is appreciated.
Lets assume a Post model. Make sure, you have
class Post < ActiveRecord::Base
acts_as_commentable
end
then in the view of say Post#show
<%= form_tag "/posts/add_new_comment" do %>
<%= hidden_field_tag "id", post.id %>
<%= text_area_tag "comment[comment]" %>
<%= submit_tag "Post Comment" %>
<% end %>
then in the PostController
def add_new_comment
post = Post.find(params[:id])
post.comments << Post.new(params[:comment])
redirect_to :action => :show, :id => post
end
and in routes.rb
match "/posts/add_new_comment" => "posts#add_new_comment", :as => "add_new_comment_to_posts", :via => [:post]
Hope this gets u up and running.
This is very, very basic stuff and you clearly need some better structure and approach to your learning. Buying a book, such as Agile Web Development with Rails, is the only real way to learn, otherwise you'll wander from problem to problem without ever actually learning anything well.
Say you have a post that you want to comment.
#routes.rb
map.resources :posts do |post|
post.resources :comments
end
#post_controller.rb
def show
#post.find params[:id]
#comment = #post.comments.new
end
#posts/show.html.erb
<%- form_for [#post, #comment] do |f|-%>
<%= f.text_area :body -%>
<%= f.submit -%>
<%- end -%>
#comments_controller
def create
#post = #post.find params[:post_id]
#comment = #post.comments.new params[:comment]
if #comment.save
redirect_to #post
This is an old question, but I want to throw in my solution as well as the gem's README is still unhelpful after all these years. It builds upon #Kunday's answer. The following will be a tutorial to use the act_as_commentable gem to...
Allow users to create comments under each post.
Show all comments belonging to a post.
This assumes that you already have a working "blog", whether it be pictures or posts. Install gem, then run rails g comment to get started.
Allow users to create comments under each post.
First, inside the model that you want to use commentable gem, add the following line as suggested in the gem's README.
class Post < ActiveRecord::Base
acts_as_commentable
end
Then create a new comment controller with the create action. Please note that the :authenticate_user! is part of devise which is a gem for creating easy authentication. current_user is also part of devise as a helper. It is needed if you want to display the user's name/email under the comment body.
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
post = Post.find_by(id: params[:id])
comment = post.comments.build(comment_params)
comment.user = current_user
if comment.save
flash[:notice] = "Comment has been created."
redirect_to post
else
flash[:alert] = "Comment has not been created."
end
end
private
def comment_params
params.permit(:comment)
end
end
Next, set up the routes. It's just this. This means that when someone sends a post request to comments, we will run to create action inside the comments controller.
post 'comments' => 'comments#create', as: "create_comment"
The as: "create_comment" gives it an alias, so you can do create_comment_path. Now, on the show view of Post, we'll add the form. The divs will help you add css.
<div class="comment-section">
<%= form_tag create_comment_path, method: "post" do %>
<%= hidden_field_tag "id", #post.id %>
<%= text_area_tag :comment %>
<%= submit_tag "Submit" %>
<% end %>
</div>
Now to show each comment under the Post show view.
The divs will help you add css, comment.user.name will work if your User class has a name column. Else, change it to email or whatever identifier you choose to use.
<div class="comment_list">
<% #comments.each do |comment| %>
<%= comment.comment %> <br>
<%= comment.user.name %> <br>
<br>
<% end %>
</div>
And finally, in order for #comments to exist in the show page, go to your Post controller, and under show, add the following:
def show
#post = Post.find_by(id: params[:id])
#comments = #post.comments.all
end
This should be good for the requirements. Good luck.