I'm developing an online store, and the customer needs the ability to delete an order and have its products automatically restocked (e.g., for test orders). Here's my first try at implementing this:
class Order < ActiveRecord::Base
def destroy_and_restock
restock_products
destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
end
end
But what if I need to create another destroy_and_x method later? Why not allow that X to be passed as a parameter to the destroy() method? So now I'm thinking of going with this:
alias :old_destroy :destroy
def destroy(options = {})
if options['restock'] == true
restock_products
end
old_destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
This is more extensible, but makes me feel somewhat dirty. Am I wrong to feel dirty? Is there a better way of doing this?
I'd say "yes, this is dirty." Your intention isn't to modify the behavior of the 'destroy' method, but rather to do some domain-specific work, then run destroy. Your first approach is great -- define a method that does what you want, and invoke destroy as needed. I think that 'wrapping' or 'monkey-patching' a method, as you're considering, is a technique that's best applied when standard OO approaches can't be used -- eg, when you need to modify/augment behavior in a class that is defined and used outside of your realm of control.
Even if you are considering modifying the behavior of the destroy method itself, I'd suggest overriding the method here, rather than wrapping it:
def destroy(options = {})
restock_products if options['restock']
super() # I think parens are necessary here, to avoid passing options up the chain
end
How about just use a block? Then you dont have to pull your hair apart while designing this in the class and you can do more as and when you need to:
def destroy_after &block
yield if block
destroy
end
Then call it like this:
order.destroy_after { order.restock_products }
I can not think of a good name for this function... but I hope you get the idea.
Horace, I misunderstood your question. I think you are looking for this:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Now you can keep your method protected and add as many before_destroy things as you like. Hope this works for you without overriding destroy.
Best of luck.
If monkey patching doesn't let you sleep at night, you can achieve the same thing by subclassing. When I'm in the need of a quick hack, or a quick debug hack, I monkey patch as well.
Related
Developing in Rails 5.2.2.1. I want to define a "global" rescue handler for my model, so that I can catch NoMethodError and take appropriate action. I find that controllers can do this with rescue_from, but models cannot. Knowing that the Rails Developers are smart people ;) I figure there must be some Good Reason for this, but I'm still frustrated. Googling around, and I can't even find any examples of people asking how to do this, and other people either telling them how, or why they can't, or why they shouldn't want to. Maybe it's because rescue handlers can't return a value to the original caller?
Here's what I'm trying to do: I need to refactor my app so that what used to be a single model is now split into two (let's call them Orig and New). Briefly, I want to make it so that when an attribute getter method (say) is called against an Orig object, if that attribute has moved to New, then I can catch this error and call new.getter instead (understanding that Orig now belongs_to a New). This solution is inspired by my experience doing just this sort of thing with Perl5's AUTOLOAD feature.
Any ideas of how to get this done are much appreciated. Maybe I just have to define getters/setters for all the moved attributes individually.
Overide method_missing :) !?
You could try overriding the method_missing method. This could potentially cause confusing bugs, but overriding that method is definitely used to great effect in at least one gem that i know of.
I didn't want to call the class new because it is a reserved keyword and can be confusing. So I changed the class name to Upgraded.
This should get you started.
class Upgraded
def getter
puts "Congrats, it gets!"
end
end
class Original
def initialize
#new_instance = Upgraded.new
end
def method_missing(message, *args, &block)
if message == :attribute_getter
#new_instance.send(:getter, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, *args)
method_name == :attribute_getter or super
end
end
c = Original.new
c.attribute_getter
You will have to change names of the getter and setter methods. Because you have a belongs_to association you can just use that.
Or you could try just using delegate_to
like #mu_is_too_short suggests, you could try something like this?
class Original < ApplicationRecord
belongs_to :upgraded
delegate :getter_method, :to => :upgraded
end
class Upgraded < ApplicationRecord
def getter_method
end
end
Apparently what I needed to know is the word "delegation". It seems there are a variety of ways to do this kind of thing in Ruby, and Rails, and I should have expected that Ruby's way of doing it would be cleaner, more elegant, and more evolved than Perl5. In particular, recent versions of Rails provide "delegate_missing_to", which appears to be precisely what I need for this use case.
I use the readonly? function to mark my Invoice as immutable after they've been sent; for by InvoiceLines, I simply proxy the readonly? function to the Invoice.
A simplified example:
class Invoice < ActiveRecord::Base
has_many :invoice_lines
def readonly?; self.invoice_sent? end
end
def InvoiceLine < ActiveRecord::Base
def readonly?; self.invoice.readonly? end
end
This works great, except that in one specific scenario I want to update an InvoiceLine regardless of the readonly? attribute.
Is there are way to do this?
I tried using save(validate: false), but this has no effect. I looked at persistence.rb in the AR source, and that seems to just do:
def create_or_update
raise ReadOnlyRecord if readonly?
...
end
Is there an obvious way to avoid this?
A (somewhat dirty) workaround that I might do in Python:
original = line.readonly?
line.readonly? = lambda: false
line.save()
line.readonly? = original
But this doesn't work in Ruby, since functions aren't first-class objects ...
You can very easily redefine a method in an instantiated object, but the syntax is definition rather than assignment. E.g. when making changes to a schema that required a tweak to an otherwise read-only object, I have been known to use this form:
line = InvoiceLine.last
def line.readonly?; false; end
Et voila, status overridden! What's actually happening is a definition of the readonly? method in the object's eigenclass, not its class. This is really grubbing around inside the guts of the object, though; outside of a schema change it's a serious code smell.
One crude alternative is forcing Rails to write an updated column directly to the database:
line.update_columns(description: "Compliments cost nothing", amount: 0)
and it's mass-destruction equivalent:
InvoiceLine.where(description: "Free Stuff Tuesday").update_all(amount: 0)
but again, neither should appear in production code outside of migrations and, very occasionally, some carefully written framework code. These two bypass all validation and other logic and risk leaving objects in inconsistent/invalid states. It's better to convey the need and behaviour explicitly in your model code & interactions somehow. You could write this:
class InvoiceLine < ActiveRecord::Base
attr_accessor :force_writeable
def readonly?
invoice.readonly? unless force_writeable
end
end
because then client code can say
line.force_writable = true
line.update(description: "new narrative line")
I still don't really like it because it still allows external code to dictate an internal behaviour, and it leaves the object with a state change that other code might trip over. Here's a slightly safer and more rubyish variant:
class InvoiceLine < ActiveRecord::Base
def force_update(&block)
saved_force_update = #_force_update
#_force_update = true
result = yield
#_force_update = saved_force_update
result
end
def readonly?
invoice.readonly? unless #_force_update
end
end
Client code can then write:
line.force_update do
line.update(description: "new description")
end
Finally, and this is probably the most precision mechanism, you can allow just certain attributes to change. You could do that in a before_save callback and throw an exception, but I quite like using this validation that relies on the ActiveRecord dirty attributes module:
class InvoiceLine < ActiveRecord::Base
validate :readonly_policy
def readonly_policy
if invoice.readonly?
(changed - ["description", "amount"]).each do |attr|
errors.add(attr, "is a read-only attribute")
end
end
end
end
I like this a lot; it puts all the domain knowledge in the model, it uses supported and built-in mechanisms, doesn't require any monkey-patching or metaprogramming, doesn't avoid other validations, and gives you nice error messages that can propagate all the way back to the view.
I ran into a similar problem with a single readonly field and worked around it using update_all.
It needs to be an ActiveRecord::Relation so it would be something like this...
Invoice.where(id: id).update_all("field1 = 'value1', field2 = 'value2'")
Here is an answer, but I don't like it. I would recommend to think twice about the design: If you make this data immutable, and you do need to mutate it, then there may be a design issue. Let aside any headache if the ORM and the datastore "differ".
One way is to use the meta programming facilities. Say you want to change the item_num of invoice_line1 to 123, you can proceed with:
invoice_line1.instance_variable_set(:#item_num, 123)
Note that the above will not work directly with ActiveRecord models' attributes, so it would need be adapted. But well, I would really advice to reconsider the design rather than falling for black magic.
Here's an elegant solution how to disallow modification generally but allow it if it is specifically requested:
In your model, add the following two methods:
def readonly?
return false if #bypass_readonly
return true # Replace true by your criteria if necessary
end
def bypass_readonly
#bypass_readonly=true
yield
#bypass_readonly=false
end
Under normal circumstances, your object is still readonly, so no risk of accidentally writing to a readonly object:
mymodel.save! # This raises a readonly error
However in privileged places where you are sure that you want to ignore the readonlyness, you can use:
mymodel.bypass_readonly do
# Set fields as you like
mymodel.save!
end
Everything inside the bypass_readonly block is now allowed despite readonly. Have fun!
This overrides the #readonly? method for one particular only, not affecting anything else:
line.define_singleton_method(:readonly?) { false }
readonly_attrs = described_class.readonly_attributes.dup
described_class.readonly_attributes.clear
# restore readonly rails constraint
described_class.readonly_attributes.merge(readonly_attrs)
This worked for us with Rails 7.
I found myself writing the following piece of code over and over again:
MyModel.find(my_id).my_field
I wonder if there is a simpler method to write this ?
If you are asking if there is more concise way of writing that.. not sure there is with the standard finders. What you have is pretty small. Just for fun I wrote this for you though :)
class ActiveRecord::Base
def self.method_missing_with_retrieve_just_a_field(method_called, *args, &block)
if(method_called.to_s=~/get_/)
self.find(args[0]).send(method_called.to_s.gsub("get_", ""))
else
method_missing_without_retrieve_just_a_field(method_called, *args, &block)
end
end
class << self
alias_method_chain :method_missing, :retrieve_just_a_field
end
end
If you put this in your config/initializers as some file like crazy_finder.rb you can then just say:
MyModel.get_my_field(my_id)
It doesnt save you much, but just thought it would be fun to write.
In addition to Jake's global solution for every model and every attribute, you can easily define explicit individual accessors:
class MyModel
def self.my_field(id)
find(id).my_field
end
end
Or an array of fields:
class MyModel
class << self
[:my_field, :other_field].each do |field|
define_method field do |id|
find(id).send(field)
end
end
end
end
Are you doing this for same resource over and over again or to many different resources? It would really help if you'd give us better example of what you're trying to do, if you're doing that many times, it would help to give us example of what you're doing many times.
If you're doing this for one resource only to get different values:
If you're doing this in same action over and over again, you're doing it wrong. First store your result in a variable like this:
#mymodel = MyModel.find(id)
and then you can use
#mymodel.my_field
and then again (without the need to find it again)
#mymodel.different_field
or if you want to do this for a collection you can do:
#mymodels = MyModel.find([my_first_id, my_second_id, my_third_id])
etc.. better example from your side would help to help you!
In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master
I am trying to DRY up my code a bit so I am writing a method to defer or delegate certain methods to a different object, but only if it exists. Here is the basic idea: I have Shipment < AbstractShipment which could have a Reroute < AbstractShipment. Either a Shipment or it's Reroute can have a Delivery (or deliveries), but not both.
When I call shipment.deliveries, I want it to check to see if it has a reroute first. If not, then simply call AbstractShipment's deliveries method; if so, delegate the method to the reroute.
I tried this with the simple code below:
module Kernel
private
def this_method
caller[0] =~ /`([^']*)'/ and $1
end
end
class Shipment < AbstractShipment
...
def deferToReroute
if self.reroute.present?
self.reroute.send(this_method)
else
super
end
end
alias_method :isComplete?, :deferToReroute
alias_method :quantityReceived, :deferToReroute
alias_method :receiptDate, :deferToReroute
end
The Kernel.this_method is just a convenience to find out which method was called. However, calling super throws
super: no superclass method `deferToReroute'
I searched a bit and found this link which discusses that this is a bug in Ruby 1.8 but is fixed in 1.9. Unfortunately, I can't upgrade this code to 1.9 yet, so does anyone have any suggestions for workarounds?
Thanks :-)
Edit: After a bit of looking at my code, I realized that I don't actually need to alias all of the methods that I did, I actually only needed to overwrite the deliveries method since the other three actually call it for their calculations. However, I would still love to know y'all's thoughts since I have run into this before.
Rather than using alias_method here, you might be better served by hard-overriding these methods, like so:
class Shipment < AbstractShipment
def isComplete?
return super unless reroute
reroute.isComplete?
end
end
if you find you are doing this 5-10 times per class, you can make it nicer like so:
class Shipment < AbstractShipment
def self.deferred_to_reroute(*method_names)
method_names.each do |method_name|
eval "def #{method_name}; return super unless reroute; reroute.#{method_name}; end"
end
end
deferred_to_reroute :isComplete?, :quantityReceived, :receiptDate
end
Using a straight eval offers good performance characteristics and allows you to have a simple, declarative syntax for what you are doing within your class definition.