rails: checking which controller method was called from within the model - ruby-on-rails

Is there a way to check which controller method was called from within the model?
Example:
Say the controller create method was called:
def create
do something
end
Then in the model do something only when create in the controller was called
if create?
do something
end

I'd imagine you could examine the call stack but this is exactly what models are not for: they should now nothing about the controller.
Examining the stack:
if caller.grep /create/
# do something
elsif caller.grep /update/
#do something else
end
Should do the trick.

Just pass a create flag to the model method, or make two different methods in the model and call the appropriate one from the controller. Otherwise you are creating a rather unpleasant dependency between the controller and the model. As you noted, validation methods take a parameter to specify when they are run.

Check
if params[:action] == 'create'

Inside your model you can ask/know if the record you are handling is a new record or not
p = Post.new
p.new_record? => true
p = Post.first
p.new_record? => false
maybe that helps you enough?
Otherwise inside a model you can add callbacks, e.g. a before_create that is only called before a new record is saved. To keep your model lean, and you should have a lot of callbacks, those could be grouped inside an observer.
Hope this helps.

Related

Using conditionals on callbacks rails

I have a callback on my comment model that creates a notification that gets sent out to the appropriate members but I don't want it to create a notification if the current_member is commenting on his own commentable object. I've tried using the unless conditional like this:
after_create :create_notification, on: :create, unless: Proc.new { |commentable| commentable.member == current_member }
def create_notification
subject = "#{member.user_name}"
body = "wrote you a <b>Comment</b> <p><i>#{content}</i></p>"
commentable.member.notify(subject, body, self)
end
But I get this error: undefined local variable or method 'current_member' for #<Comment:0x746e008
How do I get this to work like I want?
It's pretty atypical to try to use current_user or things like that from the model layer. One problem is that you're really coupling your model layer to the current state of the controller layer, which will make unit testing your models much more difficult and error-prone.
What I would recommend is to not use an after_create hook to do this, and instead create the notifications at the controller layer. This will give you access to current_user without needing to jump through any hoops.

How to implement controller in order to handle the creation of one or more than one record?

I am using Ruby on Rails 4.1. I have a "nested" model and in its controller I would like to make the RESTful create action to handle cases when one or more than one records are submitted. That is, my controller create action is:
def create
#nester = Nester.find(:nester_id)
#nesters_nested_objects = #nester.nested_objects.build(create_params)
if #nnesters_ested_objects.save
# ...
else
# ...
end
end
def create_params
params.require(:nesters_nested_object).permit(:attr_one, :attr_two, :attr_three)
end
I would like it to handle both cases when params contain data related to one object and when it contains data related to more than one object.
How can I make that? Should I implement a new controller action (maybe called create_multiple) or what? There is a common practice in order to handling these cases?
Well, if you insist on creating those records aside from their nest, I can propose to go with something like this (it better be a separate method really):
def create_multiple
#nest = Nester.find(params[:nester])
params[:nested_objects].each do |item|
#nest.nested.new(item.permit(:attr_one, :attr_two, :attr_three))
end
if #nest.save
....
else
....
end
end

Access previous value of association on record update

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.

Passing variables to Rails StateMachine gem transitions

Is it possible to send variables in the the transition? i.e.
#car.crash!(:crashed_by => current_user)
I have callbacks in my model but I need to send them the user who instigated the transition
after_crash do |car, transition|
# Log the car crashers name
end
I can't access current_user because I'm in the Model and not the Controller/View.
And before you say it... I know I know.
Don't try to access session variables in the model
I get it.
However, whenever you wish to create a callback that logs or audits something then it's quite likely you're going to want to know who caused it? Ordinarily I'd have something in my controller that did something like...
#foo.some_method(current_user)
and my Foo model would be expecting some user to instigate some_method but how do I do this with a transition with the StateMachine gem?
If you are referring to the state_machine gem - https://github.com/pluginaweek/state_machine - then it supports arguments to events
after_crash do |car, transition|
Log.crash(car: car, crashed_by: transition.args.first)
end
I was having trouble with all of the other answers, and then I found that you can simply override the event in the class.
class Car
state_machine do
...
event :crash do
transition any => :crashed
end
end
def crash(current_driver)
logger.debug(current_driver)
super
end
end
Just make sure to call "super" in your custom method
I don't think you can pass params to events with that gem, so maybe you could try storing the current_user on #car (temporarily) so that your audit callback can access it.
In controller
#car.driver = current_user
In callback
after_crash do |car, transition|
create_audit_log car.driver, transition
end
Or something along those lines.. :)
I used transactions, instead of updating the object and changing the state in one call. For example, in update action,
ActiveRecord::Base.transaction do
if #car.update_attribute!(:crashed_by => current_user)
if #car.crash!()
format.html { redirect_to #car }
else
raise ActiveRecord::Rollback
else
raise ActiveRecord::Rollback
end
end
Another common pattern (see the state_machine docs) that saves you from having to pass variables between the controller and model is to dynamically define a state-checking method within the callback method. This wouldn't be very elegant in the example given above, but might be preferable in cases where the model needs to handle the same variable(s) in different states. For example, if you have 'crashed', 'stolen', and 'borrowed' states in your Car model, all of which can be associated with a responsible Person, you could have:
state :crashed, :stolen, :borrowed do
def blameable?
true
end
state all - [:crashed, :stolen, :borrowed] do
def blameable?
false
end
Then in the controller, you can do something like:
car.blame_person(person) if car.blameable?

link to a controller function from the view

I have a function to take ownership of a job which updates the database to update the username in a table row. I want to link to this function from the view and then redirect to the appropriate page.
How do you link to a controller function or a model function from the view?
from the index i want to have another link beside show, edit, delete, which says 'take ownership'
This will then fire off an action in the application controller
def accept_job(job_type, id, username)
if (job_type == 'decom')
Decommission.update(id, :username => username)
else
end
end
You can use the instance variable #controller to get a reference to the controller. As for calling a model function, you can call Model.function to call class methods, or if you have a particular Model instance called model_instance, then use model_instance.function to call an instance method.
Edit: Okay, I think I understand what you're asking now.
You should
Create a new action in the controller, let's call it update_username:
def update_username
job = Job.find(params[:id])
job.your_method #call your method on the model to update the username
redirect_to :back #or whatever you'd like it to redirect to
end
Add your action the routes in routes.rb. See Rails Routing from the Outside In for more details.
Add your link in the view:
<%=link_to "Update my username please!", update_username_job_path%>
First you create a function in your model, say
class Decommission
def assign_permission(name)
#your update code
end
end
As I can see, you can do this in 3 different ways
1 - Create a helper method to update the permission (This can be done either in Application helper or helper related to your view)
2 - By creating a controller method (as you proposed) But if you are not using this method in other views you dont need to create this method in application controller
3 - If you want to use your method in both controllers and views, create your method in application controller and make it as helper method. By that way you can access it from controllers as well as views
cheers
sameera

Resources