I'm very confused about this. My model has the following custom validation:
def custom_validation
errors[:base] << "Please select at least one item" if #transactionparams.blank?
end
Basically it's checking to make sure that certain parameters belonging to a different model are not blank.
def request_params
#requestparams = params.require(:request).permit(:detail, :startdate, :enddate)
#transactionparams = params["transaction"]
#transactionparams = #transactionparams.first.reject { |k, v| (v == "0") || (v == "")}
end
If it's not blank, then what happens is that the record is saved, and then all kinds of other things happen.
def create
request_params
#request = #user.requests.create(#requestparams)
if #request.save
...
else
render 'new'
end
end
If the record is not saved, the re-rendered new view then shows what the errors are that stopped #request from being created. The problem is that whether or not #transactionparams.blank? is true or false, the record always fails to save, and I checked this specifically with a puts in the log.
What's happening? I read through the docs because I thought that maybe custom validators couldn't be used on other variables... but that's not the case...
Thanks!
OK actually read up on related articles. It's bad practice to ever access a variable from the controller in the model. That's why... If i put the puts inspection in the model not controller, #transactionparams is always nil.
Related
Need help understanding this code, as what to my knowledge I know "<<" append to a collection but here it saves the record correctly, how come it does without calling .save method?
#user.rb
has_many :saved_properties, through: :property_saves, source: :property
#users_controller.rb
def update
if #user.saved_properties << Property.find(params[:saved_property_id])
render plain: "Property saved"
end
In the has_many documentation it says:
Adds one or more objects to the collection by setting their foreign
keys to the collection's primary key. Note that this operation
instantly fires update SQL without waiting for the save or update
call on the parent object, unless the parent object is a new record.
Maybe looking at the source code will help you. This is my trail of searches based on the << method in activerecord:
def <<(*records)
proxy_association.concat(records) && self
end
rails/collection_proxy.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def concat(*records)
records = records.flatten
if owner.new_record?
load_target
concat_records(records)
else
transaction { concat_records(records) }
end
end
rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def concat_records(records, should_raise = false)
result = true
records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
end
end
result && records
end
rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
set_inverse_instance(record)
if raise
record.save!(validate: validate)
else
record.save(validate: validate)
end
end
https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_association.rb#L32
def insert_record(record, validate = true, raise = false)
ensure_not_nested
if record.new_record? || record.has_changes_to_save?
if raise
record.save!(validate: validate)
else
return unless record.save(validate: validate)
end
end
save_through_record(record)
record
end
https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_through_association.rb#L38
As you can see, in the end, it does call the save method.
Disclaimer: I'm not that familiar with Rails souce code, but you have interesting question.
In a has_many relationship the link information is saved in the target record. This means that << would have to modify that record in order to add it to the set.
Perhaps intending convenience, ActiveRecord automatically saves these for you when making an assignment if the assignment was successful. The exception is for new records, the record they're being associated with doesn't have any identifier so that has to be delayed. They are saved when the record they're associated with is finally created.
This can be a little confusing, perhaps unexpected, but it's actually the thing you'd want to happen 99% of the time. If you don't want that to happen you should manipulate the linkage manually:
property = Property.find(params[:saved_property_id])
property.user = #user
property.save!
That's basically equivalent but a lot more verbose.
I have a concern that checks addresses and zip codes with the intention of returning an error if the zip code does not match the state that is inputed. I also don't want the zip code to save unless the problem gets fixed.
The problem that I am having is that it appears that the if I submit the form, the error message in create pops up and I am not able to go through to the next page, but then somehow the default zip code is still saved. This only happens on edit. The validations are working on new.
I don't know if I need to share my controller, if I do let me know and I certainly will.
In my model I just have a
include StateMatchesZipCodeConcern
before_save :verify_zip_matches_state
Here is my concern
module StateMatchesZipCodeConcern
extend ActiveSupport::Concern
def verify_zip_matches_state
return unless zip.present? && state.present?
state_search_result = query_zip_code
unless state_search_result.nil?
return if state_search_result.upcase == state.upcase
return if validate_against_multi_state_zip_codes
end
errors[:base] << "Please verify the address you've submitted. The postal code #{zip.upcase} is not valid for the state of #{state.upcase}"
false
end
private
def query_zip_code
tries ||= 3
Geocoder.search(zip).map(&:state_code).keep_if { |x| Address::STATES.values.include?(x) }.first
rescue Geocoder::OverQueryLimitError, Timeout::Error
retry unless (tries -= 1).zero?
end
def validate_against_multi_state_zip_codes
::Address::MULTI_STATE_ZIP_CODES[zip].try(:include?, state)
end
end
I have two fields in a form I would like to validate the presence of, before sending. The problem though is that the controller's model doesn't have these fields in the database, so I tried making virtual attributes. I'm not quite sure how to get it to work though.
I tried doing this in the Model, called "Find_number"
class FindNumber < ActiveRecord::Base
attr_accessor :name
attr_accessor :original_number
validates :name, presence: true
validates :original_number, presence: true
end
and the following in the create action of the Find_numbers controller
def create
#user = current_user
client = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
search_params = {}
%w[in_postal_code near_number contains].each do |p|
search_params[p] = params[p] unless params[p].nil? || params[p].empty?
end
local_numbers = client.account.available_phone_numbers.get('US').local
#numbers = local_numbers.list(search_params)
if :name.valid? && :original_number.errors.any?
unless #numbers.empty?
render 'find_numbers/show'
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
end
When I enter info though, I get the error
undefined method `valid?' for :name:Symbol
I'm probably calling the :name and :original_number attributes incorrectly, and the double if then statements look very newbish :P.
What would I need to replace if :name.valid? && :original_number.errors.any? , to make sure that it validates? Or is there a lot more I'm missing?
I think you are confusing the boundary between 'controller' and 'model'. The controller doesn't know anything about the validations inside of the model that you've written. The fact that the controller is called FindNumbersController and the model is called FindNumber doesn't really mean anything in terms of shared functionality.
You would need to explicitly create an instance of the model with the passed in params, and let the model perform the validation on the instance
find_number = FindNumber.new(params.slice(:name, :original_number))
Then you can ask whether the instance as a whole is valid
find_number.valid?
or whether a specific field has any error messages
find_number.errors[:field].any?
So, :name.valid? becomes find_number.errors[:name].empty? and :original_number.errors.any? becomes find_number.errors[:original_number].any?
I have a model where if it is given a certain status, the status can never be changed again. I've tried to achieve this by putting in a before_save on the model to check what the status is and raise an exception if it is set to that certain status.
Problem is this -
def raise_if_exported
if self.exported?
raise Exception, "Can't change an exported invoice's status"
end
end
which works fine but when I initially set the status to exported by doing the following -
invoice.status = "Exported"
invoice.save
the exception is raised because the status is already set the exported on the model not the db (I think)
So is there a way to prevent that attribute from being changed once it has been set to "Exported"?
You can use an validator for your requirement
class Invoice < ActiveRecord::Base
validates_each :status do |record, attr, value|
if ( attr == :status and status_changed? and status_was == "Exported")
record.errors.add(:status, "you can't touch this")
end
end
end
Now
invoice.status= "Exported"
invoice.save # success
invoice.status= "New"
invoice.save # error
You can also use ActiveModel::Dirty to track the changes, instead of checking current status:
def raise_if_exported
if status_changed? && status_was == "Exported"
raise "Can't change an exported invoice's status"
end
end
Try this, only if you really want that exception to raise on save. If not, check it during the validation like #Trip suggested
See this page for more detail.
I'd go for a mix of #Trip and #Sikachu's answers:
validate :check_if_exported
def check_if_exported
if status_changed? && status_was.eql?("Exported")
errors.add(:status, " cannot be changed once exported.")
end
end
Invalidating the model is a better response than just throwing an error, unless you reeeally want to do that.
Try Rails built in validation in your model :
validate :check_if_exported
def check_if_exported
if self.exported?
errors.add(:exported_failure, "This has been exported already.")
end
end
I have a Rails app that lets a user construct a database query by filling out an extensive form. I wondered the best practice for checking form parameters in Rails. Previously, I have had my results method (the one to which the form submits) do the following:
if params[:name] && !params[:name].blank?
#name = params[:name]
else
flash[:error] = 'You must give a name'
redirect_to :action => 'index'
return
end
But for several form fields, seeing this repeated for each one got tiresome. I couldn't just stick them all in some loop to check for each field, because the fields are set up differently:
a single key: params[:name]
a key and a sub-key: params[:image][:font_size]
only expect some form fields to be filled out if another field was set
Etc. This was also repetitive, because I was setting flash[:error] for each missing/invalid parameter, and redirecting to the same URL for each one. I switched to using a before_filter that checks for all necessary form parameters and only returns true if everything's okay. Then the my results method continues, and variables are just assigned flat-out, with no checking involved:
#name = params[:name]
In my validate_form method, I have sections of code like the following:
if (
params[:analysis_type][:to_s] == 'development' ||
params[:results_to_generate].include?('graph')
)
{:graph_type => :to_s, :graph_width => :to_s,
:theme => :to_s}.each do |key, sub_key|
unless params[key] && params[key][sub_key]
flash[:error] = "Cannot leave '#{Inflector.humanize(key)}' blank"
redirect_to(url)
return false
end
end
end
I was just wondering if I'm going about this in the best way, or if I'm missing something obvious when it comes to parameter validation. I worry this is still not the most efficient technique, because I have several blocks where I assign a value to flash[:error], then redirect to the same URL, then return false.
Edit to clarify: The reason I don't have this validation in model(s) currently is for two reasons:
I'm not trying to gather data from the user in order to create or update a row in the database. None of the data the user submits is saved after they log out. It's all used right when they submit it to search the database and generate some stuff.
The query form takes in data pertaining to several models, and it takes in other data that doesn't pertain to a model at all. E.g. graph type and theme as shown above do not connect to any model, they just convey information about how the user wants to display his results.
Edit to show improved technique: I make use of application-specific exceptions now, thanks to Jamis Buck's Raising the Right Exception article. For example:
def results
if params[:name] && !params[:name].blank?
#name = params[:name]
else
raise MyApp::MissingFieldError
end
if params[:age] && !params[:age].blank? && params[:age].numeric?
#age = params[:age].to_i
else
raise MyApp::MissingFieldError
end
rescue MyApp::MissingFieldError => err
flash[:error] = "Invalid form submission: #{err.clean_message}"
redirect_to :action => 'index'
end
You could try active_form (http://github.com/cs/active_form/tree/master/lib/active_form.rb) - just ActiveRecord minus the database stuff. This way you can use all of AR's validation stuff and treat your form like you would any other model.
class MyForm < ActiveForm
validates_presence_of :name
validates_presence_of :graph_size, :if => # ...blah blah
end
form = MyForm.new(params[:form])
form.validate
form.errors
Looks like you are doing the validation in the controller, try putting it in the model, it's better suited to that sort of thing.
If you were to tackle the problem again today, you could create a model for the query parameter set and use Rails' built in validations, Rails 3 makes this a lot easier with ActiveModel::Validations see this post.
Model
class Person
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name
attr_accessor :email
validates_presence_of :name,:message => "Please Provide User Name"
validates_presence_of :email,:message => "Please Provide Email"
end
Note that you don't necessarily need to save/persist the model to validate.
Controller
#person.name= params["name"]
#person.email= params["email"]
#person.valid?
One you called .valid? method on the model, the errors will be populated inside #person object. Hence,
View
<%if #person.errors.any? %>
<%#person.errors.messages.each do|msg| %>
<div class="alert alert-danger">
<%=msg[0][1]%>
</div>
<%end%>
<%end%>