In my rails app, I wish for the users to be able to upload multiple files at once.
I am using the carrierwave gem
gem 'carrierwave', github: 'carrierwaveuploader/carrierwave'
On the master branch which supposedly supports the multiple: true
found out in my internet searching desperation about the necessary addition of optional: true in my model for my attachment.
I'm using mysql so I can't use an array type in my database, but I've set up a has_many, belongs_to relationship between Request and Request_Attachment in my database.
my form:
<%= f.fields_for :request_attachments do |ra| %>
<div class="row" id="uploader">
<div class="col-xs-12">
<label class="btn btn-info"> Upload Files
<%= ra.file_field :file, multiple: true, name: "request_attachments[file][]", :style => "display: none" %>
</label>
</div>
</div>
<% end %>
my controller
#request = Request.new(request_params)
if #request.save
if params[:request][:request_attachments]
params[:request][:request_attachments]['file'].each do |f|
#request_attachment = #request.request_attachments.create!(:file => f)
end
end
flash[:success] = "Your request was submitted successfully, check your email for a confirmation message."
redirect_to action: 'index', status: 303
else
render :new
end
def request_params
params.require(:request).permit(:jobtitle, :requester, :status, request_attachments_attributes: [:id, :request_id, :file])
end
my models:
Request:
class Request < ApplicationRecord
has_many :request_attachments
accepts_nested_attributes_for :request_attachments
end
Request_Attachments:
class RequestAttachment < ApplicationRecord
mount_uploader :file, FileUploader
belongs_to :request, optional: true
end
I have tried variations of the form, like taking out the name portion.
When I remove the multiple: true portion, it will work perfectly, but it does not work with the multiple option.
I'm not quite sure where the issue may be, so any help would be great.
What is happening now is that the request gets saved, but
Only 1 request_attachment is created, and
The filename of the request_attachment is nil
Okay, I think I figured it out.
I made a couple changes, which threw an error, and I looked closely at the Parameter Hash, and I noticed essentially it looked like this:
{"utf8"=>"✓",
"request"=>
{"jobtitle"=>"Testing, again and again, and again",
"request_attachments_attributes"=>
{"0"=>
{"file"=>
[#<ActionDispatch::Http::UploadedFile:0x007fee9df806b8
#content_type="image/gif",
#headers="Content-Disposition: form-data; name=\"blah\"; filename=\"logo1.gif\"\r\nContent-Type: image/gif\r\n",
#original_filename="logo1.gif",
#tempfile=blah,
#<ActionDispatch::Http::UploadedFile:0x007fee9df80690
#content_type="image\gif",
#headers=
"Content-Disposition: form-data; name=\"blah\"; filename=\"Blah.gif\"\r\nContent-Type: image/gif\r\n",
#original_filename="Blah.gif",
#tempfile=blahblah]}}
I noticed the "0"=> and thought "wait.... what is that doing there?....
So I removed the name section in my form, and changed my controller to look like this:
if #request.save
params[:request][:request_attachments_attributes]["0"]['file'].each do |f|
#request_attachment = #request.request_attachments.create!(:file => f)
end
And that seemed to have worked.
Related
I have a website, where you can register for some events I called talks. After the registration the "guest" gets a confirmation email with a generated unsubscribe link.
The link opens a form with all the events listed that the guest registered for.
Now to unsubscribe from an event the guest has to uncheck the checkbox of the event. But I want it inverted so that they have to check the event to unsubscribe from it.
The models
class Guest < ApplicationRecord
has_and_belongs_to_may :talks
end
class Talk < ApplicationRecord
has_and_belongs_to_may :guests
end
The unsubscribe form
<%= form_for #guest do |f| %>
<% f.collection_check_boxes :talk_ids, #guest.talks, :id, :name do |b| %>
<%= b.check_box %>
<%= b.object.name %>
<% end %>
<input type="submit" value="Unsubscribe">
<% end %>
The update function after submitting
def update_talks
#guest = Guest.find(params[:id])
if #guest.update(guest_params)
redirect_to root_url
else
flash[:notice] = "Failed"
end
end
I couldn't find a solution so I hope you guys can tell me how to invert the checkbox?
Been a while, but I just ran into a similar issue where in some cases it is better for the interface to understand if the checked checkbox sets the boolean in the database to false.
This is the Rails 7 check_box method form_helper.rb:
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
So you only need to change the checked_value to 0, the unchecked_value to 1 and toggle the checked option. In my case (imagine "method" is the boolean in the database:
= f.check_box :method, { checked: !f.object.method? }, '0', '1'
I'm a very beginner in Rails, but maybe it'll be better if the checkboxes come already checked
I'm trying to write an edit action for a my post object which is nested inside the projects one. By the looks of it I'm doing everything as it should be but for some reason when I comes down to load the form, this error message appears "First argument in form cannot contain nil or be empty"
The params being sent are correct "project_id"=>"308", "id"=>"41", however it seems like the form does not know what to do with them.
form
.....
<div class="row">
<div class="col-md-12" id="postform">
<%= form_for ([#project, #post]), html: { multipart: true } do |f| %>
....
view
....
<% if current_user?(post.user) %>
<%= link_to edit_project_post_path(#project, post ) do %>
....
controller
....
before_action :logged_in_user, only: [:create, :edit]
def edit
#project = Project.find(params[:project_id])
#post = #project.posts.find(params[:id])
end
....
post is a nested object of projects and so it has been config on the route s file.
Any ideas what might be causing this problem?
I thought that maybe I need to specify on the form what are these values used for...
Thanks for the help
---- Edit ----
resources :projects do
resources :comments,:posts
end
resources :posts, only: [:create, :destroy, :edit]
form_for is not supposed to work with multipart: true, try removing that or try with form_tag.
Best regards.
Really new to RoR, and ran into an issue I can't find the answer to anywhere.
I have two tables, user and books, and I'm trying to use a join table to match up the user and the books and add a rating and review column in the join table.
The problem is everything passes into the join table EXCEPT the book_id
View
<%= form_for(current_user.user_book_collections.build) do |f| %>
<div>
<%= hidden_field_tag :book_id, current_book.id %>
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :review %> (optional):
<%= f.text_area :review, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
</div>
<p></p>
<%= f.submit "Add to your collection", class:"btn btn-primary" %>
<% end %>
current_user and current_book are methods where the users and books are assigned respectively.
Controller
def create
#user_book_collection = current_user.user_book_collections.build(user_book_collection_params)
if #user_book_collection.save
flash[:success] = "Added to your collection"
redirect_to mybooks_path
else
flash[:danger] = "Add was unsuccessful"
redirect_to bookcollection_path
end
end
private
def user_book_collection_params
params.require(:user_book_collection).permit( :book_id, :user_id, :review, :rating )
end
end
This is what shows up in the console:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"85gmzUO7ldQrevh/qnKwYO9mkd9lX77sG3xxJQV8Y46xZrkl5ifk665abPr79nOT91rO3oLcMSDgYL7BtR+/XQ==", "book_id"=>"6", "user_book_collection"=>{"rating"=>"s", "review"=>"asd"}, "commit"=>"Add to your collection"}
but then checking the record also in the console i can see the book_id did not pass through even though its been assigned in the parameters:
=> #<ActiveRecord::Associations::CollectionProxy [#<UserBookCollection id: 10, user_id: 7, book_id: nil, review: "asd", rating: "s", created_at: "2016-02-07 06:36:33", updated_at: "2016-02-07 06:36:33">
Any help would be appreciated, thanks.
It is because your form is constructed in a way the book_id is passed in params[:book_id], not in params[:user_book_collection][:book_id] thus not present in user_book_collection_params
Use:
f.hidden_field :book_id, value: current_book.id
<%= f.hidden_field :book_id, current_book.id %>
When using form_for, you need to bind the input method to the form object (as above).
In addition to Rich Peck and Vasfeds excellent answers I would suggest you consider what you are doing from a REST point of view.
You want users to be able to add reviews to a book. Note that here UserBookCollections is just an ugly piece of plumbing - not really a resource which you would expose with REST. And calling it UserBookCollection is a bit misleading since each UserBookCollection is really just a link between one user and one book.
Also you want to avoid calling anything Collection in Rails since ActiveRecord uses the concept to denote a collection of linked records which may or may not be loaded - you're going to confuse others.
This is one way to solve the same problem
class User
has_many :reviews
has_many :books, through: :reviews
end
class Review
belongs_to :user
belongs_to :book
end
class Book
has_many :reviews, through: :reviews
end
Nice, now we have an object that we don't even have to explain what it does. So lets turn it into a RESTful resource:
Rails.application.routes.draw do
resources :reviews, only: [:show, :edit, :update, :destroy]
resources :books do
resources :reviews, only: [:new, :create, :index], module: :books
end
resources :users do
resources :reviews, only: [:index], module: :users
end
end
This gives us routes with context:
Prefix Verb URI Pattern Controller#Action
edit_review GET /reviews/:id/edit(.:format) reviews#edit
review GET /reviews/:id(.:format) reviews#show
PATCH /reviews/:id(.:format) reviews#update
PUT /reviews/:id(.:format) reviews#update
DELETE /reviews/:id(.:format) reviews#destroy
book_reviews GET /books/:book_id/reviews(.:format) books/reviews#index
POST /books/:book_id/reviews(.:format) books/reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) books/reviews#new
user_reviews GET /users/:user_id/reviews(.:format) users/reviews#index
Cool - now we have an API for Review with a built in context. The RESTful routes themselves tell us when we are creating a review for a certain book or looking at reviews by a certain user.
module: :books tells rails to link the nested routes to a "namespaced" controller instead of shoving everything into ReviewsController.
We setup our controllers as follows:
class ReviewsController < ApplicationController
before_action :set_review, only: [:edit, :show, :update, :destroy]
# GET /reviews/:id
def show
end
# GET /reviews
def index
#reviews = Review.all
end
# GET /reviews/:id/edit
def edit
end
# PATCH /reviews/:id
def update
#review.update(review_params)
respond_with(#review)
end
# DELETE /reviews/:id
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:rating, :body) # note that you don't need book_id in here.
end
end
# app/controllers/books/reviews_controller.rb
class Books::ReviewsController < ReviewsController
before_action :set_book
# GET /books/:book_id/reviews
def index
#reviews = Review.eager_load(:user, :book).where(book_id: params[:book_id])
end
# GET /books/:book_id/reviews/new
def new
#review = #book.find(params[:book_id]).reviews.new
end
# POST /books/:book_id/reviews
def create
#review = #book.reviews.new(review_params) do |review|
review.user = current_user
end
respond_with(#review)
end
private
def set_book
#book = Book).find(params[:book_id])
end
end
# app/controllers/users/reviews_controller.rb
class Users::ReviewsController < ApplicationController
# GET /users/:user_id/reviews
def index
#reviews = Review.joins(:user, :book).where(user_id: params[:user_id])
end
end
You might question why you would want to use 3 controllers? It allows for a very clean mechanism of both code sharing and customizations. You can have different logic and views without creating a bunch of if or switch code paths. Having many branches in a single action makes testing really messy and violates the skinny controllers paradigm.
The forms would look something like this:
# app/views/reviews/_fields.html.erb
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :body %> (optional):
<%= f.text_area :body, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
# app/views/books/reviews/new.erb
<%= form_for([#book, #review]) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>
# app/views/books/edit.erb
<%= form_for(#review) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>
I was following the instructions on the wiki for this gem:
https://github.com/mislav/will_paginate/wiki
And it works great for my listings controller :/ I was able to get them paginate but I have no idea how to get it work with my comments. My comments controller doesn't have a show method.
class CommentsController < ApplicationController
before_action :authenticate_user!, :except => [:show]
def create
#comment = Comment.new(params[comment_params])
#comment.listing_id = params[:listing_id]
#comment.user_id = current_user.id
#comment.body = params[:comment][:body]
if #comment.save
flash[:success] = "Comment Successful."
redirect_to listing_path(#comment.listing)
else
flash[:alert] = "Comment Failed."
end
end
def destroy
#comment = #listing.comments.find(params[:id])
#comment.destroy
flash[:success] = "Comment Removed."
redirect_to listing_path(#listing)
end
private
def comment_params
params.require(:comment).permit(:user, :body)
end
end
This is the show method I have inside my ListingsController:
def show
#comment = Comment.new
#comment.listing_id = #listing_id
end
It's what sets the comments to my understanding
This is the listings/show.html.erb file's part where it renders the comments:
<div class="commentblock">
<div class="text-left">
<%= render partial: 'comments/form' %>
<%= render partial: 'listings/comment', collection: #listing.comments.reverse %>
</div>
</div>
I know the first part of ruby code actually renders the forms but I don't understand how or what the collection part works or how to apply the pagination to this.
The listings/comment file looks like this:
<div class="panel panel-default">
<div class="panel-heading"><%= comment.user.username %>: <%= time_ago_in_words(comment.created_at)%> ago</div>
<div class="panel-body">
<%= comment.body %>
</div>
</div>
I've tried quite a few things but I still don't really get where or what to change in my code. I went into the listing/comment and tried adding:
<%= will_paginate(comment) %> # this crashed, undefined method `total_pages' for #<Comment:0x007ff556a36468>
EDIT: The will_paginate wiki says you to pass a paginated array when you get the error I got, but I don't know that is done >.>; it doesn't show how you would do that...
EDIT:
http://csnipp.com/s/709 this website said all you had to do was create a file config/initializers/will_paginate.rb and place this line of code in it:
require 'will_paginate/array'
But that didn't help
EDIT:
If you guys need to see more of the code it's actually on github:
https://github.com/ilovemysillybanana/pastie/
I'm really stuck on this D: I followed a tutorial on how to make the comments but theirs didn't paginate.
EDIT:
I fixed it!
The way to do it was by replacing this line:
<%= render partial: 'listings/comment', collection: #listing.comments.reverse %>
with this line:
<%= render partial: 'listings/comment', collection: #list_comments = #listing.comments.paginate(:page => 1, :per_page => 5) %>
But for some reason I don't get the numbers to change the comment pages :/
<%= render partial: 'listings/comment', collection: #listings = #listing.comments.reverse.paginate(:page => params[:page], :per_page => 10) %>
My code was correct, what I wasn't realizing is that #listings or #list_comments was creating a local variable that needed to be paginated itself. I was trying to find a way to paginate either in one command(which if possible I don't care, but if you know how it'd be nice to know for the future I guess) anyway, all that I needed to do to fix this was add this:
<%= will_paginate #listing_comments %>
I've been staring at this for a while and Google hasn't helped much so I'm turning to you guys for help. This should be a pretty simple fix.
The goal of the code is to take an email address from a sign up field and place it in the database.
I think most of what I need is there but I'm getting this error:
undefined method model_name for NilClass:Class
My home.html.erb file contains the following:
<%= form_for(#signup) do |f| %>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit "Enter" %>
</div>
<% end %>
The model contains this:
class Signup < ActiveRecord::Base
attr_accessible :email
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates(:email, :presence => true,
:length => {:maxiumum => 40},
:format => {:with => email_regex})
end
The controller contains:
class SignupController < ApplicationController
def show
end
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:id])
if #signup.save
else
render 'new'
end
end
end
The problem is most likely because of an instance variable that you're using in your form_for not being set to an ActiveRecord object. It appears that you are setting it correctly in your "new" action, but it's not clear that you're rendering the correct template since you mention the form being in "home.html.erb"?
Either way, ensure that whatever you're using in the form_for is set to a valid ActiveRecord object and your problem may be solved.
In addition, you may want to change your create action to use all the params from the form:
#signup = Signup.new(params[:signup])
Add this to the home action in the pages controller:
#signup = Signup.new
The reason for the error is that you are using #signup in your form, but you didn't define it in your show controller action.