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
Related
I have a few hours with something that is probably very easy.
I have a nested model
resources :grades do
resources :students
end
So I defined
before_action :set_grade, except: [:mass_input]
to my students_controller
def set_grade
#grade = Grade.find(params[:grade_id])
end
I'm very good with this, the problem is that now I'm using another action that takes :grade_id from another source, so I cant use set_grade, instead I'm passing the id with javascript. Works.
My problem appears here, when I try to call to create method, I'm probably doing it wrong ..
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
#is this create way ok or I'm overriding???
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
end
This is my create action
def create
#student = Student.new(student_params)
#grade.students << #student
respond_to do |format|
if #student.save
format.html { redirect_to school_grade_path(#grade.school,#grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
By this way code works but this line is not working
#grade.students << #student
#grade is not passing from mass_input to create. I think I'm not calling create properly but I cant find how to do it , because is not redirecting neither
My mass_input action is working by this way
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
grade.students << student
respond_to do |format|
if student.save
format.html { redirect_to school_grade_path(grade.school,grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: student }
else
format.html { render :new }
format.json { render json: student.errors, status: :unprocessable_entity }
end
end
end
but I think is AWFUL, I must use my own create action
Thanks!!
Oh... From my point of view you are doing smth strange... The fast solution for your issue would be smth like this:
1) Rewrite before action in a new way:
before_action :set_grade
And method set_grade:
def set_grade
#grade = Grade.find(params[:grade_id].presence || #data['grade'])
end
2) Set method for student params
def student_params
data = JSON.parse(params[:form_data])['mass_students']
#Transform data to be student params. For ex:
data.map{|_key, info| {:rut => info[0], :nombre => info[1], :apellido => info[2]}}
end
3) Rewrite mass_input method
def mass_input
respond_to do |format|
if (#students = #grade.students.create(student_params).all?(&:persisted?)
#some actions when everything is great.
else
#some actions if not of them valid (maybe redirect & show info about not created students)
end
end
end
But you should definetly read more rails guides... http://guides.rubyonrails.org/
Sorry, I couldn't comment it. So I can just post a reply, it is not an complete answer though. In the student controller
Try to use
#student = #grade.students.new
or
#student = Student.new
#student.grade = #grade or #student.grade_id = params[:grade_id]
So when you do #student.save, you won't need to do the line below, and it will still work
#grade.students << #student
Ruby on rails has conventions you should follow to simplify lots of things. The first thing I see here is that in your def mass_input, you are using
Student.create(...)
The method create, as it says, creates an object but also saves it into database. So you should have new instead of create because new does not save it to database, just instantiates it:
#student = Student.new
...inside def mass_input, and by default the submit action in your view will take your object to the create method (if the object is new it goes to create, other way it goes to update, thanks to Rails). For this you could take a look at http://guides.rubyonrails.org/action_controller_overview.html
About the line #grade.students << #student, I assume you are intending to add the newly created student to his grade. See this example of usage of nested resources when trying to create, edit or destroy http://railscasts.com/episodes/139-nested-resources. In any case, nested resources implies this:
class Grade < ActiveRecord::Base
has_many :student
end
class Student < ActiveRecord::Base
belongs_to :grade
end
So, in your model Student you should have a column to store the Grade of that student. And then in your params you should receive the actual grade and store it in the grade_id inside your #student.
If something is not clear, I suggest you to take a look at the nested resources guide http://guides.rubyonrails.org/routing.html#nested-resources
As a commentary, << is used to add "things" to the end of an array, i.e. if you want to quickly store in an array some info you use:
array = []
Student.all.each do |s|
array << s.name
end
It will store in the array all the names of your students. Obviously there is a simpler way to do this by doing this:
Student.pluck(:name)
I have a new select box appearing after edit. The model that I modified in the edit is another model using fields_for option.
Someone mentioned that I had the problem with the new and create actions in the controller.
the current controller:
def new
#print = Print.new
end
def create
#print = Print.new(params[:print])
#print.user_id = current_user.id
if #print.save
redirect_to print_path(#print), :flash => { :success => "Successfully created your Print Order." }
else
render :action => 'new'
end
end
def edit
#print = Print.find(params[:id])
#print.blackwhites.build
end
The fields_for that edit data from the model:
def index
def new
#blackwhite = Blackwhite.new
end
def create
#blackwhite = Blackwhite.new(params[:blackwhite])
#blackwhite.print_id = #print.id
end
def update
#blackwhite = Blackwhite.find(params[:id])
end
def show
#blackwhite = Blackwhite.find(params[:id])
end
def edit
#blackwhite = Blackwhite.find(params[:id])
end
Edit:
Fixed the problem.
The first thing I see:
def create
#blackwhite = Blackwhite.new(params[:blackwhite])
#blackwhite.print_id = #print.id
render :action => 'new' <<<< ?????
end
Try
redirect_to print_path(#print)
That would be the typical default thing to do, show the data that just got created, or in your nested case, show the parent of the record that just got created. All you really need to do is STOP rendering the new action after you create, that's NOT right!
The functionality I'm trying to build allows Users to Visit a Restaurant.
I have Users, Locations, and Restaurants models.
Locations have many Restaurants.
I've created a Visits model with user_id and restaurant_id attributes, and a visits_controller with create and destroy methods.
Thing is, I can't create an actual Visit record. Any thoughts on how I can accomplish this? Or am I going about it the wrong way.
Routing Error
No route matches {:controller=>"restaurants", :location_id=>nil}
Code:
Routes:
location_restaurant_visits POST /locations/:location_id/restaurants/:restaurant_id/visits(.:format) visits#create
location_restaurant_visit DELETE /locations/:location_id/restaurants/:restaurant_id/visits/:id(.:format) visits#destroy
Model:
class Visit < ActiveRecord::Base
attr_accessible :restaurant_id, :user_id
belongs_to :user
belongs_to :restaurant
end
View:
<% #restaurants.each do |restaurant| %>
<%= link_to 'Visit', location_restaurant_visits_path(current_user.id, restaurant.id), method: :create %>
<% #visit = Visit.find_by_user_id_and_restaurant_id(current_user.id, restaurant.id) %>
<%= #visit != nil ? "true" : "false" %>
<% end %>
Controller:
class VisitsController < ApplicationController
before_filter :find_restaurant
before_filter :find_user
def create
#visit = Visit.create(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
respond_to do |format|
if #visit.save
format.html { redirect_to location_restaurants_path(#location), notice: 'Visit created.' }
format.json { render json: #visit, status: :created, location: #visit }
else
format.html { render action: "new" }
format.json { render json: #visit.errors, status: :unprocessable_entity }
end
end
end
def destroy
#visit = Visit.find(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
#restaurant.destroy
respond_to do |format|
format.html { redirect_to location_restaurants_path(#restaurant.location_id), notice: 'Unvisited.' }
format.json { head :no_content }
end
end
private
def find_restaurant
#restaurant = Restaurant.find(params[:restaurant_id])
end
def find_user
#user = current_user
end
end
I see a lot of problems here. The first is this line of code in your VisitController's create action (and identical line in your destroy action):
#visit = Visit.create(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
params is a hash, so you should be passing it a key (if anything), not a bunch of key => value bindings. What you probably meant was:
#visit = Visit.create(:user_id => #user.id, :restaurant_id => #restaurant.id)
Note that you initialize #user and #restaurant in before filter methods, so you don't need to access params here.
This line of code is still a bit strange, though, because you are creating a record and then a few lines later you are saving it (if #visit.save). This is redundant: Visit.create initiates and saves the record, so saving it afterwards is pretty much meaningless. What you probably want to do is first initiate a new Visit with Visit.new, then save that:
def create
#visit = Visit.new(:user_id => #user.id, :restaurant_id => #restaurant.id)
respond_to do |format|
if #visit.save
...
The next thing I notice is that you have not initiated a #location in your create action, but you then reference it here:
format.html { redirect_to location_restaurants_path(#location), notice: 'Visit created.' }
Since you will need the location for every restaurant route (since restaurant is a nested resource), you might as well create a method and before_filter for it, like you have with find_restaurant:
before_filter :find_location
...
def find_location
#location = Location.find(params[:location_id])
end
The next problem is that in your view your location_restaurant_path is passed the id of current_user and of restaurant. There are two problems here. First of all the first argument should be a location, not a user (matching the order in location_restaurant_path). The next problem is that for the _path methods, you have to pass the actual object, not the object's id. Finally, you have method: :create, but the method here is referring to the HTTP method, so what you want is method: :post:
link_to 'Visit', location_restaurant_visits_path(#location, restaurant.id), method: :post
You'll have to add a find_location before filter to your RestaurantController to make #location available in the view here.
There may be other problems, but these are some things to start with.
location_id is nil and the path definition doesn't say (/:location_id) forcing a non-nil value there in order to route to that path; create a new route without location_id if you can derive it from a child's attribute (i.e. a restaurant_id refers to a Restaurant which already knows its own location_id).
In my create action, method new acts like create.
def create
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
if #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
ActiveRecord creates new object in database while I'm using new with params. Page.new works fine in new action in my controller. What can be the reason? There is no overridden method new and no callbacks (before_save, before_create etc) in my model. Any help would be appreciated.
UPDATE - code from debugger
.../app/controllers/pages_controller.rb:48
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
(rdb:25) #page
nil
(rdb:25) n
.../app/controllers/pages_controller.rb:49
if #page.save
(rdb:25) #page
#<Page id: 80 ... >
(rdb:25) Page.last
#<Page id: 80 ... >
(rdb:25) #page.save
false
Check my inline comments..
def create
#page = Page.new(params[:page].merge(:user_id => current_user.id )) # you trigger new thats fine..
if #page.save # Notice here.. This line is triggering query on database.
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
Reason (method in model which can change status in workflow):
def status=(state_name)
states = [self.current_state.to_sym]
possible_states.each {|t| states<< t[1]}
unless state_name.blank?
if states.include? state_name
process_event! state_name
end
end
end
Ugly fix
def create
#page = Page.new
if #page.update_attributes(params[:page].merge(:user_id => current_user.id )) && #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
Mistake was quite silly and I'm not proud of my solution. Anyway, thanks for help:)
With an ActiveRecord class, create = new + save
https://github.com/rails/rails/blob/7edade337e968fb028b2b6abfa579120eb424039/activerecord/lib/active_record/persistence.rb#L40
Your controller code is correct. This is how a 'create' controller method should work. The problem is not there.
Are you certain you're having two models created?
The .new method you're calling with the attributes creates an activerecord object in memory that's unsaved. The .save method saves it. At the end (assuming the data is valid) you should have a single object in memory.
If you have two objects created, then there is a problem. If you have only one, then it's as it should be.
Are you having a second object created by this controller method?
The process should be:
# when GET /student/new is called, this returns an empty object to display in the form
# for the user to see.
def new
#page = Page.new
end
# When POST /page is called, the form params are passed in here.
def create
# First, generate a new page object with the params passed in.
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
# Now try save the object to persist it in the database.
if #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
I have two models, one is for pages and one is for authors.
I have nearly got it to show which author belongs to the pages but keep getting an error, saying:
uninitialized constant Page::Authors
I have defined a method for Authors in my pages controller #pages = #author.pages and not sure if it is right. hear is my code for my controllers.
class PagesController < ApplicationController
def index
#pages = Page.find(:all, :order => 'created_at DESC')
end
def show
#page = Page.find(params[:id])
end
def new
#page = Page.new
end
def edit
#page = Page.find(params[:id])
end
def create
#page = Page.new(params[:page])
if #page.save
redirect_to(#page, :notice => 'Page was successfully created.')
else
render :action => "new"
end
end
def update
#page = Page.find(params[:id])
if #page.update_attributes(params[:page])
redirect_to(#page, :notice => 'Page was successfully updated.')
else
render :action => "edit"
end
end
def destroy
#page = Page.find(params[:id])
#page.destroy
end
def author
#pages = #author.pages
end
end
And here is my Authors controller:
class AuthorsController < ApplicationController
def index
#authors = Author.find(:all, :order => 'created_at DESC')
end
def show
#author = Author.find(params[:id])
end
def new
#author = Author.new
end
def edit
#author = Author.find(params[:id])
end
def create
#author = Author.new(params[:author])
if #author.save
redirect_to(#author, :notice => 'Author was successfully created.')
else
render :action => "new"
end
end
def update
#author = Author.find(params[:id])
if #author.update_attributes(params[:author])
redirect_to(#author, :notice => 'Author was successfully updated.')
else
render :action => "edit"
end
end
def destroy
#author = Author.find(params[:id])
#author.destroy
end
def author_id
#author = #pages.author
end
end
I just want to display who has created the pages, I am using a select from the form to get the author which has already been entered into the database. When you create a new page the select list is selected and the content is entered and then when you show the new or show template that is when the error occurs.
In my models I have said belongs_to :author and has_many :pages.
I am close.
Any ideas would be great. thanks
What's missing here is the stack-trace that shows where the line in question is exercised. It is common to make the mistake of referencing the Authors class instead of the actual Author and end up with errors like this. Nothing in what you've pasted seems to be problematic in that way.
If Author has_many :pages and Page belongs_to :author then this should work.