I need to validate a field before the create method
In my _form.html.erb I have two models, one is the owner model, and the other is a model I create to have other arguments, I need to validate those arguments before getting in the create method, I can use an if, but it is not the best practice to do it.
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
city = city_params()
#customer.city_id = City.find_by(name: city["name"]).id
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
I need to validate the the city name, because the customer owner must have the city_id, and the _form requests the name (string), so I need to find the city but previously I need to validate the city name has a value and it exists,
How can I validate this in the model ?
If I were you, I would start out by keeping all of this logic in the controller and use a filter to find the city:
class CustomersController < ApplicationController
before_action :find_city, only: [:create, :update]
def create
#customer = Customer.new(customer_params)
#read the city name, since this is requested by city name (string) and it shoud be "id" in the system
#customer.city_id = #city.try(:id) # This returns `nil` if the city was not found
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render action: 'show', status: :created, location: #customer }
else
format.html { render action: 'new' }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
private
def find_city
#city = City.find_by(name: params[:city][:name]) # No need for strong parameters for this
end
end
Then make sure you're validating the presence of city_id in your Customer class:
class Customer < ActiveRecord::Base
validates :city_id, presence: true
end
Later on, if you find that you need this logic to be extracted from the controller, then consider looking at creating a service object or a form object. Because this is a simple case (only 2 classes are involved), I would hold off on creating those constructs for now though. The controller layer is sufficient enough to handle this simple logic.
Why not move the logic directly into the model? I can tell you from experience that you do not want to mess your model up with tons of logic involving other model classes. Customer should not really know much about City in my opinion.
before_validate
You could use the before_validate callback in your model:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
attr_accessor :city_name
before_validate :set_city
private
def set_city
city_id = City.find_by(name: city_name).id
end
end
--
Custom Validation Method
I think the bottom line is you'll be best using a custom validation method for this. You basically want to return the user to the form with an error saying "City not found" or similar; which is entirely within the remit of a custom validation method:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
validate :check_city_id
private
def check_city_id
errors.add(:city_id, "City doesn't exist") unless City.try city_id
end
end
--
System
This kind of issue can be handled by simply giving the user options to select the id at input; rather than selecting by name:
#app/views/customers/new.html.erb
<%= form_for #customer do |f| %>
<%= f.select :city_id, City.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
I think your method of giving the user the ability to pick a city name, and then validating in the backend is very inefficient; whilst giving the user a rigid set of options to select a buyer by city is far more robust
We have something called callbacks http://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html ..using this we can trigger our required validations in the model.
You can create your own logic of validation like example
before_create :method_name
def method_name
your logic.....example: validates :city_name ..
end
Related
First steps with RoR, trying to wrap my head around basic concepts. Following excercise: I have pupils and schoolclasses, both Active Record entities with a many to many (has_and_belongs_to_many) to each other. Now I have a form to create a new pupil. On this form there is also a form.select to pick the class for the pupil, but I can´t get this to work, I can´t get the controller to create a new record for the join table.
Schoolclass.rb
class Schoolclass < ApplicationRecord
has_and_belongs_to_many :pupils
end
Pupil.rb
class Pupil < ApplicationRecord
has_and_belongs_to_many :schoolclasses
end
Relevant part of the _form.html.erb
<div class="field">
<%= form.label :schoolclass %>
<%= form.select(schoolclass.id, schoolclasses_for_select) %>
</div>
schoolclasses_for_select is just a helper for populating the select box
def schoolclasses_for_select
Schoolclass.all.collect{ |s| [s.name, s.schoolyear] }
end
Everything I have tried on the controller has failed miserably. Somehow, I mostly end up with the controller trying to pass the schoolclass (as a String) as an attribute to the new Pupil, or with a MethodNotFound error. In my understanding it should work something like this :
#klass = params[:schoolclass]
pupil.schoolclasses << #klass
but it doesn´t.
Thanks in advance for any help.
Edit1: the create code
def create
#pupil = Pupil.new(pupil_params)
respond_to do |format|
if #pupil.save
format.html { redirect_to #pupil, notice: 'Pupil was successfully created.' }
format.json { render :show, status: :created, location: #pupil }
else
format.html { render :new }
format.json { render json: #pupil.errors, status: :unprocessable_entity }
end
end
end
def pupil_params
params.require(:pupil).permit(:nachname, :vorname, :schoolclass)
end
That is the part that works. What I haven't managed is to find the correct Schoolclass record and pass it to the pupil.
Issues
First argument to your form.select should be the field name i.e. :schoolclass_id. You can still keep the label Schoolclass.
I believe you want id of schoolclass to be passed in params when selected. For that to happen, change your options for select to Schoolclass.all.collect{ |s| [s.name, s.id] }
Biggest, Your association says a pupil can have multiple schoolclasses but your form doesn't support it. Have you handled it some other way?
Fixes
So, do something like (this does not support multiple schoolclasses selection):
<%= form.select :schoolclass_id, Schoolclass.all.collect{ |s| [s.name, s.id] } %>
And in your controller
def create
#pupil = Pupil.new(pupil_params)
# Find schoolclass from `schoolclass_id` and associate it to `#pupil`
schoolclass = Schoolclass.find(params[:pupil][:schoolclass_id]) # Handle case when schoolclass not selected in form
#pupil.schoolclasses |= [schoolclass]
respond_to do |format|
...
end
end
private
def pupil_params
params.require(:pupil).permit(:nachname, :vorname)
end
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)
In my rails application, there is a model named Message and other is Organization. In the message controller the data is inserted in the table messages, one of the attribute that is saved is no_of_message that is being counted in the create action itself. In the organizations table there are two fields promo_limit and trans_limit.
If sms_type is "TRANS" I want to subtract the value of trans_limit field from no_of_message and update it.
Similarly if sms_type is "PROMO", than the value of promo_limit has to be subtracted and updated.
Following are the parameter received by message_params
Parameters: {"utf8"=>"✓", "authenticity_token"=>"QHUDS5jfZNvPb4bnASyxjWgKwFaLF0/LSYUY+qHH/109e0xlLsxSsGUluCjVCeLBrK6ga6np64mHRlQ9sLSBbA==", "message"=>{"message_text"=>"sample message", "sms_type"=>"PROMO", "sendto"=>"Teaching", "sent_to"=>"34434343243, 7869851872", "organization_id"=>"4"}, "contact_nos"=>["34434343243, 7869851872"], "contact_no"=>["34434343243", "7869851872"]}
Following is the create action
def create
#message = Message.new(message_params)
#message.no_of_message = #message.sent_to.try(:split,",").try(:count)
#message.sent_to = message_params[:staff_type]
respond_to do |format|
if #message.save
format.html { redirect_to #message, notice: 'Message was successfully created.' }
format.json { render :show, status: :created, location: #message }
else
format.html { render :new }
format.json { render json: #message.errors, status: :unprocessable_entity }
end
end
end
Rails models
Class Message
belongs_to :organization
Class Organization
has_many :messages
Assuming your message table has an organization_id in the DB. Rails associations will allow you to create/update and retrieve relational object. If your messages belong to an organization, when you create a new message you would need to assign it to an organization. Before you can assign it an organization you would either need to know the id of the organization you want to attach the message to or you could retrieve the organization object by any field that is defined on the object. So if you had a unique field called name on your organization you could say
org =Organization.find_by_name("foo")
#message.organization = org
if #message.save
if params['sms_type'] == "TRANS"
org.trans_limit = #message.no_of_message - org.trans_limit
elsif params['sms_type'] == "BAR"
#same thing for other business logic
end
org.save
end
The point is you have to get the organization object base on the business logic for your use case and then you can manipulate it and save it.
Here is a link to the rails association guide
http://guides.rubyonrails.org/association_basics.html
Im currently trying to convert my price_in_cents field to the virtual attribute of price_in_dollars. I did some research and basically implemented everything from the railscast virtual attributes video, here is the link below.
https://www.youtube.com/watch?v=Tr7tD2GPiXU
At first, the column in my db was 'amount' for the money field. So I ran a
$ rails g migration add_price_in_cents_to_payments price_in_cents:integer
then,
$ rake db:migrate
My application is just a basic CRUD scaffold for creating new payments.
I added the getter setter methods into the payment.rb file, like so.
class Payment < ActiveRecord::Base
attr_accessible :amount, :price_in_dollars, :from, :to
# The price_in_dollars attribute is taking the place of :amount
def price_in_dollars
price_in_cents.to_d/100 if price_in_cents
end
def price_in_dollars=(dollars)
self.price_in_cents = dollars.to_d*100 if dollars.present?
end
end
And changed the form field to represent the new price_in_dollars attribute.
<div class="field">
<%= f.label :price_in_dollars %><br />
<%= f.text_field :price_in_dollars %>
</div>
But now when I submit a new payment, it returns the NoMethodError for my "new" and "create" methods in the payments controller. In the video, Bates does not even touch his controller. Here is my controller.
def new
#payment = Payment.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #payment }
end
end
# GET /payments/1/edit
def edit
#payment = Payment.find(params[:id])
end
# POST /payments
# POST /payments.json
def create
#payment = Payment.new(params[:payment])
respond_to do |format|
if #payment.save
format.html { redirect_to #payment, notice: 'Payment was successfully created.' }
format.json { render json: #payment, status: :created, location: #payment }
else
format.html { render action: "new" }
format.json { render json: #payment.errors, status: :unprocessable_entity }
end
end
end
Do I need to specify to pass in the (params[:price_in_dollars]) in all my CRUD methods in the controller?
I'm a novice with only about 3 weeks of rails knowledge. Please help anyway possible.
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).