when i'm trying to edit my Students and change the subjects the have, the last student i edited loses all his subjects... Can Somebody Help me? Example: Added Math to Josh. Added Math and History to Jenny. Josh's subjects are now empty.
#students_controller
def edit
#subjects = Subject.all
end
def update
#subjects = Subject.find(subjects_params)
#subjects.each do |subject|
#student.subjects << subject
#student.save
end
if #student.update(student_params)
flash[:success] = "Success"
redirect_to students_path
else
flash[:danger] = "Error"
render :new
end
end
Not clearly sure what logic must be implemented, but i think you need to add subjects_attributes to student_params method. And also add accept_nested_attributes_for :subjects in Student model
After it you can do something like this
def update
#student.update(student_params)
end
This will add needed subjects to selected student.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html more info about nested attributes
Also you can see simple_nested_form gem which wraps all nested attributes.
Related
I want to save two models in one controller action, or save neither, and return with the validation errors.
Is there a better way than this?
def update
#job = Job.find(params[:id])
#location = #job.location
#job.assign_attributes(job_params)
#location.assign_attributes(location_params)
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
if #job.valid? && #location.valid?
#job.save
#location.save
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
else
render 'edit'
end
end
New version:
def update
#job = Job.find(params[:id])
#location = #job.location
begin
Job.transaction do
#job.assign_attributes(job_params)
#job.save!(job_params)
#location.assign_attributes(location_params)
#location.save!(location_params)
end
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
rescue ActiveRecord::RecordInvalid => invalid
render 'edit'
end
end
Have a look at Active Record Nested Attributes.
Using Nested attributes, you can save associated record attributes through parent.If parent record fails, associated records won't be saved.!
the first thing you'd want to do is delete these two lines
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
and only keep the #save in the if statement. because if one of them is valid, but the other isn't, you'll still save the valid one to the db.
To answer your second question, is there a better way to do this? At first blush, it looks like a job for #accepts_nested_attributes_for. However, accepts_nested_attributes_for is somewhat notorious for being difficult to get working (really it just takes a fare amount of tinkering) and what you're currently doing should get you where you're trying to go, so it's up to you.
You can use validates_associated rails helper:
class Model < ActiveRecord::Base
has_one :location
validates_associated :location
end
Then:
if #job.save
#blah
else
#blah
end
Is enough without having to mess with ActiveRecord#Nested_attributes. It's fastest, but less cleaner. Your choice.
Reference:
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
Rails 3.2. I am using the following code to associate user_id to the record:
# review.rb
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true, :counter_cache => true
end
# reviews_controller.rb
def create
#review = #reviewable.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
I want to find a way to use this, but it keeps saying that the current_user is not defined, but I could find the current_user object:
def create
#review = #reviewable.current_user.reviews.create(params[:review]
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
If you can post your code to what the #reviewable object is, it might help to give a more specific answer. But if you want a one liner, you can do something like this:
#review = #reviewable.reviews.build(params[:review].merge({:user_id => current_user.id})
But personally, i think your original looks better as it's easier to read.
As an additional note, your second example also calls create and save. You don't need to call both, as create saves the object when accepting a hash of parameters. Save is nice to use if you want to initialize an object, modify it in some way, then save it later.
I think that the reason this does not work is because current_user is a method that is not defined on a reviewable.
The reviewable may belong to a user, in which case #reviewable.user.reviews.create... may be valid.
What I want is to create a profile pages, where i can view the previous book field and if the customer change the text then it would create a new books.
I have the following models with has_many relationship
Customer -- ID, First, Last, Email
Book -- ID, Description
Book_Managers -- ID, Customer_id, Book_id, Visible
Right now what i have is a customer edit which allow me to see multiple form by rendering from many more models like books, phones, etc...
Here my customer Controller
def edit
#customer = Customer.find(params[:id])
if #customer.books.any?
#book = #customer.books.order("created_at DESC").first
else
#book = #customer.books.build
end
end
What i would like to see is if i created a new instance when going to book form i should see the last and able to modify "The JavaScript Bible" to something "The Java Bible" and it would not update it but just create a new version. Right now when going to the form book i see nothing. And if i do for some odd reason it was only allowing me to update.
class BooksController < ApplicationController
def create
#book = current_customer.books.build(params[:book])
if #book.save
flash[:success] = "Book Created"
redirect_to root_url
else
render 'customer/edit'
end
end
def index
#books = Book.all
end
def destroy
#book.destroy
redirect_to root_url
end
end
ADDED THIS
def update
#book = current_customer.books.build(params[:book])
if #book.save
flash[:success] = "Book Updated"
redirect_to root_url
else
render 'customer/edit'
end
end
To my book controller, the only problem right now is my association, i can't seem to find any book with the current customer. is there somethign wrong with my query?
There is some gems for versioning. This that : https://www.ruby-toolbox.com/categories/Active_Record_Versioning
You can do something like this :
def update
params = params[:book].merge(:previous_version => params[:id])
#book = current_customer.books.create(params[:book])
end
It will create a new book on each update. The last version will be the book without "previous_version".
I have a model called Details that contains a method that updates some attributes with calculations by using another model's (Summary) attributes. I call that method into a before_saveto make Details' attributes update automatically when I modify an entry. I would like to make my Details' attributs update when I modify my entries in the Summary model. What I'm searching is something that could be used like that :
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
(Details.update_all_attributes)
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end
See the line : Details.update_all_attributes. All my Details' attributs would be saved and recalculated because of the before_save I have put. How can I do that ? Thanks.
Edit :
I just found the answer. I did a loop that saves all my entries.
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
#details = Details.all
#details.each do |detail|
detail.save!
end
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end
Hope I will help some of you !
Fat models, skinny controllers:
Model
class Summary < ActiveRecord::Base
after_update :recalculate_details
private
def recalculate_details
Detail.all.each {|d| d.save! }
end
end
Controller
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end
How can I go about restricting the number of reviews a user can write for a venue to just one?
I would also like a flash message prompt if they try to review a venue twice.
I'm not too sure what code I should to include in my question but heres the create review method:
Review controller
def create
#review = current_user.reviews.create!(params[:review])
#review.venue = #venue
if #review.save
flash[:notice] = 'Thank you for reviewing this venue!'
respond_to do |format|
format.html { redirect_to venue_path(#venue) }
format.js
end
else
render :action => :new
end
end
Thanks for any help its much appreciated!
edit
I've added this helper method into the venues controller:
def reviewed?
if current_user.reviews.for_venue(#venue).empty?
true
else
false
end
end
and wrapped my form in:
<% if reviewed? %>
form
<% end %>
but this just returns undefined method `reviews' for VenuesController
It would be better if you could prevent the user from reviewing the venue in the first place. In your view(or create a helper) do a check if the venue was already reviewed by the user(many ways to do this). If it was, don't show the review form. Easy. If you're pretty OC, that's when you check in the controller.
Maybe you should use a before_validation callback, so that you can check if the venue has been already reviewed by the user. You can include the venue in the create line:
current_user.reviews.create!({:venue_id => #venue.id}.merge(params[:review]))
And for the validation, use something like this:
before_validation :check_if_already_reviewed
def check_if_already_reviewed
if (check if already reviewed)
return false
else
return true
end
end