A model method starts with the following logic:
def calculate_vat
if self.country.blank?
flash[:danger] = "Please select country first"
redirect_to edit_organization_path(self) and return
end
if ["BE", "CZ", "ES", "LV", "LT"].include? self.country
return 0.21
elsif etc. for other countries.
end
end
Is this not possible within a model method? In the controller, the model method gets called upon using #organization.model_method. It produces the following error in development: undefined local variable or method 'flash' for #<Organization:0x00000007298a68>.
Update: I understand now that it's impossible to use flash and redirect in model method. But how to solve this? Only a specific group of users will ever deal with this model method; so I don't want to make country required for all users. A custom requirement seems impossible because there are no model variables upon which to base such a validation, it's a matter whether a user will ever get to certain checkout pages.
Would the best solution perhaps be to define a private controller method that is called upon from inside the controller just before calculate_vat is called upon?
This is senseless.
Fire the desired flash message in the controller's action after the method is being called and that's it.
Actually, everything you do in the model_method is a pure validation, so just define it properly:
validates :country, presence: true
Related
I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.
I've been trying to figure out how to update a field in a table when a condition becomes true.
My site checks if a workout has been completed by comparing two arrays. When the completedExercises array has the same workout_id's as the totalExercisesInWorkout array, it comes back as true. When I get true, I want to assign the user a new workout_id.
I think the best way to do this is with an after_save callback in the model, something like this:
after_save :is_workout_complete?
protected
def is_workout_complete?
if #totalExercisesInWorkout.included_in?(#completedExercises) == true
current_user.workout_id = #workout.next_workout_id
end
end
The problem is, The instance variables I'm calling are defined in the application_controller in an initialize_vars method, which isn't accessible here.
Should I define these instance variables somewhere else? I need them is several places
Is this the best way to check that the last exercise has been completed, then take an action on the user?
So the problem here was that I was defining things in the controller that really belonged in the model. If you want to access the current user, you need to write your method in the User model. An example is:
def add_initial_program
prog_id = Program.for_user(self).first.id
self.user_programs.build( :cycle_order => 1, :group_order => 1, :workout_order => 1, :program_id => prog_id)
end
In the above example I simply use self to refer to the User class, which is the current signed in user.
I have a "event" model that has many "invitations". Invitations are setup through checkboxes on the event form. When an event is updated, I wanted to compare the invitations before the update, to the invitations after the update. I want to do this as part of the validation for the event.
My problem is that I can't seem to access the old invitations in any model callback or validation. The transaction has already began at this point and since invitations are not an attribute of the event model, I can't use _was to get the old values.
I thought about trying to use a "after_initialize" callback to store this myself. These callbacks don't seem to respect the ":on" option though so I can't do this only :on :update. I don't want to run this every time a object is initialized.
Is there a better approach to this problem?
Here is the code in my update controller:
def update
params[:event][:invited_user_ids] ||= []
if #event.update_attributes(params[:event])
redirect_to #event
else
render action: "edit"
end
end
My primary goal is to make it so you can add users to an event, but you can't not remove users. I want to validate that the posted invited_user_ids contains all the users that currently are invited.
--Update
As a temporary solution I made use for the :before_remove option on the :has_many association. I set it such that it throws an ActiveRecord::RollBack exception which prevents users from being uninvited. Not exactly what I want because I can't display a validation error but it does prevent it.
Thank you,
Corsen
Could you use ActiveModel::Dirty? Something like this:
def Event < ActiveRecord::Base
validates :no_invitees_removed
def no_invitees_removed
if invitees.changed? && (invitees - invitees_was).present?
# ... add an error or re-add the missing invitees
end
end
end
Edit: I didn't notice that the OP already discounted ActiveModel::Dirty since it doesn't work on associations. My bad.
Another possibility is overriding the invited_user_ids= method to append the existing user IDs to the given array:
class Event < ActiveRecord::Base
# ...
def invited_user_ids_with_guard=(ids)
self.invited_user_ids_without_guard = self.invited_user_ids.concat(ids).uniq
end
alias_method_chain :invited_user_ids=, :guard
end
This should still work for you since update_attributes ultimately calls the individual attribute= methods.
Edit: #corsen asked in a comment why I used alias_method_chain instead of super in this example.
Calling super only works when you're overriding a method that's defined further up the inheritance chain. Mixing in a module or inheriting from another class provides a means to do this. That module or class doesn't directly "add" methods to the deriving class. Instead, it inserts itself in that class's inheritance chain. Then you can redefine methods in the deriving class without destroying the original definition of the methods (because they're still in the superclass/module).
In this case, invited_user_ids is not defined on any ancestor of Event. It's defined through metaprogramming directly on the Event class as a part of ActiveRecord. Calling super within invited_user_ids will result in a NoMethodError because it has no superclass definition, and redefining the method loses its original definition. So alias_method_chain is really the simplest way to acheive super-like behavior in this situation.
Sometimes alias_method_chain is overkill and pollutes your namespace and makes it hard to follow a stack trace. But sometimes it's the best way to change the behavior of a method without losing the original behavior. You just need to understand the difference in order to know which is appropriate.
In my calendar application the date is stored as a text_field
= text_field :task_time, :day, :value => display_date(#date), :id => "date-n"
I read this date in the controller's index method but then am pre-generating default time as today in the model:
private
def generate_task_time
self.task_time = Time.now if self.task_time.nil?
end
I need to change it and set default task_time to the time currently displayed in the calendar, but am getting undefined local variable or method `params' error when trying to read it in the model: convert_to_string(params[:task_time]
Is it possible to do it in the model at all or is it better to remove generate_task_time and do everything in the controller?
Any suggestion on the syntax -
DateTime.strptime("#{params[:date]}")
or
convert_to_string(params[:task_time]) unless params[:task_time].nil?
params is an instance method on the controller, so you won't have access to it in the model. You can pass the value of params[:task_time] to the model, though:
task.task_time = convert_to_string(params[:task_time]) if params[:task_time]
It's perfectly valid to set attributes on the model from the controller. This is a common Rails pattern for updating the model attributes in a controller action:
if model.update_attributes(params[:model])
# do something success-y
else
# do something fail-y
end
But if you start doing too much in the controller, you get tied up with the request lifecycle and have a hard time testing your actual business logic. It's always about balance!
I'm using Authlogic. I've got an Entry model and there is a validation method that I want to skip if the request was made by a logged in user (anonymous users are also allowed to create entries). I assume that UserSession.find would not be usable from inside the Entry model like it is from a controller. What is the best way to handle this situation? Do I need to write a custom save method of some sort and call it programmatically from the EntriesController? Or is there a way to check for login state from inside a model? I'm guessing that I wouldn't want to do that anyway, since the model should be in charge of object state and not application state.
Create a virtual attribute on Entry called logged_in.
attr_accessor :logged_in
In the controller, set entry.logged_in to true if the user is logged in, false if not. Then you can use that attribute in the validation method.
validate_on_create :my_check
def my_check
unless self.logged_in?
# add errors
end
end
You can use
valide_on_create :my_check, :unless => logged_in?