Rails calling create method from same controller instance variable - ruby-on-rails

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)

Related

Keep changes on reload if validation fails

I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.

:id => "info" error rails wicked forms when retrieving params

I'm new to wicked form and I was following the railcast episode on wicked forms but I keep receiving this error "Couldn't find Company with 'id'=info". So I know that the problem is clearly in my controllers somewhere. I know it's something super simple that I'm just racking my brain on so I know you guys will be a giant help. Here is the code, any and all help appreciated!
Code for companies Controller:
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
#object = #company.id
format.html { redirect_to(company_steps_path(#company)) }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
Code for company_steps Controller:
class CompanyStepsController < ApplicationController
include Wicked::Wizard
steps :info, :address, :quote
def show
#company = Company.find(params[:id])
render_wizard
end
def update
#company = Company.where(id: params[:id])
#company.attributes = params[:company]
render_wizard #company
end
end
When you use #find and the record is not found ActiveRecord raise a ActiveRecord::RecordNotFound with a message like "Couldn't find Company with id='somevalue'".
I assume your id column is of type integer and you pass a string.
In your #show method params[:id] == 'info'.
Check your link_to, redirect_to and routes.
At some point you generate this url http://localhost:3000/company_steps/info (probably in a view).
You do a GET request on it, which match GET "/company_steps/:id" company_steps#show.
The method #show is call in the controller CompanyStepsController with params[:id] == 'info'.
As we see previously you get a ActiveRecord::RecordNotFound exception because ActiveRecord can't find the record with a id 'info'.
The error is raise in your controller, but the problem is probably in your views or in a redirect. You need a id and you pass a string.
EDIT: as discussed in comments
Ok params[:id] == 'info' is generated by wicked.
They use id to control the flow of steps.
You need to use nested routes to have rails generate something like params[:company_id].
resources :companies do
resources :steps, controller: 'companies/steps'
end
So rake routes should give you:
/companies/:company_id/steps/:id
in the controller
params[:company_id] == 42
params[:id] == 'info'
https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step

How to use parameters value from public_activity gem (Rails)?

I am building a Rails app. And in my app, there are Projects where users can "Follow". When a user follows one of the pages, he/she will get updates if somebody uploads/creates a folder/file.
Below is the screenshot when somebody just created a new folder:
And below is the code for "Create" action in my Folder controller:
def create
#folder = current_user.folders.where(project_id: params[:project_id]).create(folder_params)
respond_to do |format|
if #folder.save
#folder.create_activity :create, owner: current_user, :params => {
:project_id => proc {|controller, project| #folder.project.id},
:project_name => proc {|controller, project| #folder.project.name},
}
format.html { redirect_to #folder.project, notice: 'Folder was successfully created.' }
format.json { render :show, status: :ok, location: #folder }
else
format.html { render :new }
format.json { render json: #folder.errors, status: :unprocessable_entity }
end
end
end
As you can see :project_id and :project_name are the parameters for the public_activity when a new folder being created.
And below is the screenshot on how this parameters value looks like in the database after they were saved:
QUESTION:
So my question is, how do i use this parameters values in my activities_controller?
Here is the code for my activities controller right now:
class ActivitiesController < ApplicationController
def index
#activities = PublicActivity::Activity.order("created_at desc").where(owner_id: current_user.following_users, owner_type: "User")
end
end
Instead of using "owner_id:", I want to use the "project_id" value from parameters column. So how can i do this?
Thank you very much in advanced! :)
The parameters field contains a simple yaml dump, so not really easy to search efficiently.
A simple solution would be to use LIKE operator, for instance
PublicActivity::Activity.where("parameters LIKE '%project_id: #{#project.id}%'")
You might want to consider to add custom fields instead.
Thanks for the answer, but I got a better solution than using the parameters value or custom field.
Here is how my activities_controller looks like right now:
class ActivitiesController < ApplicationController
def index
activity_table = PublicActivity::Activity.arel_table
# We want to view all activity of folders related to projects we are follwing
folder_ids = Folder.where(project_id: current_user.following_projects.pluck(:id)).pluck(:id)
# Generate query for all activity related to folders we care about
folders_query = activity_table[:trackable_type].eq('Folder').and(
activity_table[:trackable_id].in(folder_ids)
)
# Generate query for all users that we follow
users_query = activity_table[:owner_id].in(current_user.following_users.pluck(:id))
activity_query = folders_query.or(users_query)
#activities = PublicActivity::Activity.where(activity_query)
end
end
By using this way, I could easily combine the activities from the "Users" and also from the "Projects" that the user follows.
You can modify it to add any other activities such as from the "Comments" or "Voting".
Hope this will help other people out there that are using public_activity gem! :)

Rails 4 & Controller action

I am trying to make an app in Rails 4.
I have three models - Project, Scope and Finalise.
The associations are:
Project has one scope
Scope belongs to project and has one finalise
Finalise belongs to Scope
Scope accepts nested attributes for Finalise.
Within Finalise, I have an attribute called :draft (boolean) and an attribute called :finalised_at (datetime)
I tried to write a function that would let me put a link on any project created with :draft saved as true. The link is meant to toggle :draft to false, which then displays a publication date (:finalised_at) on completed projects.
I am getting an error which is preventing me from creating a test project. The error is: undefined local variable or method `create_a_finalise' for - referring to the create action in my project controller. I think this has something to do with the create_a_finalise method being in my scope.rb model. I don't know how to fix this.
My finalise.rb has:
after_validation :set_publish_time
def set_publish_time
self.finalised_at = Time.now unless self.draft
end
My scope.rb has:
after_create :create_a_finalise
def create_a_finalise_dynamic(boolean)
self.finalise.create draft: boolean
end
My finalise_controller has:
def toggle_draft
#finalise = Finalise.find(params[:finalise_id])
#finalise.draft = false
if #finalise.save
redirect_to project_path(#finalise.scope.project), notice: 'Successfully Updated'
else
redirect_to project_path(#finalise.scope.project), alert: 'Not Updated'
end
end
The problematic part of my project_controller has:
def create
#authorise #project
#project = Project.new(project_params)
#project.creator_id = current_user.id
#project.users << current_user
respond_to do |format|
if #project.save
format.html { redirect_to #project }
format.json { render action: 'show', status: :created, location: #project }
else
format.html { render action: 'new' }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
Can anyone see what I need to do to be able to create a new project. There is something wrong with the create action in my projects controller.
Thank you
You are referring to create_a_finalise but it is named create_a_finalise_dynamic.
after_create :create_a_finalise
def create_a_finalise_dynamic
Change the name of the method to
def create_a_finalise
and leave off the argument.
Give the following a try-
# scope.rb
after_create :create_a_finalise
def create_a_finalise(val)
self.create_finalise(draft: val)
end

Create rails record from two ids

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).

Resources