Atomic arithmetic in Rails - sql

I need to perform some atomic arithmetic in Rails but the only way I've found to do it for single objects is via the crude update_all class method, e.g.:
Account.update_all(["debits = debits + ?", amount], :id => id)
With collection associations, the update_all class method should be usable as an association method, since the collection will pass missing method calls on to the class with the relevant scope:
accounts.update_all(["debits = debits + ?", amount])
When dealing with collections, this is much nicer and less repetitive. However this doesn't work for singleton associations, i.e. belongs_to and has_one. The method_missing for AssociationProxy passes through to the target instance, which won't have an update_all instance method (naturally).
Is there a more elegant way to perform this arithmetic? Or is update_all as good as it gets?

I think what you are looking for is the update_counters method. It allows you to specify an arbitrary increment value and still performs an atomic operation:
Account.update_counters id, :debits => amount
It also supports performing the operation on collections of ids without a loop:
Account.update_counters [1, 2, 3, 4, 5], :debits => amount
Check out the most recent Rails 3.0.x documentation:
http://api.rubyonrails.org/classes/ActiveRecord/CounterCache.html#method-i-update_counters

You're not going to beat the performance of update_all on a large number of records by a longshot. Any reason not to use that and wrap it in a transaction?

Related

Rails; How to select for a single database value with a scope

I have a database called students with columns like so
In my rails model with a scope or function, I would like to select for the single age value based on the id. The id column is unique. So something like
scope :get_age, -> (id) { where(id: id).select(:age) }
However this does not seem to work. I would like for the returned value to just be the int 12. Using something like pluck ends up returning an array which I would like to avoid. How would I go about selecting for just the value of 12?
you know that for one id there is just one row (or no rows) so using where or pluck is not ideal, we want something it returns one row or nothing (find, find_by etc)
def self.get_age(id)
find_by(id: id)&.age
end
some_id = 10
User.get_age(some_id)
Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as where, joins and includes. All scope bodies should return an ActiveRecord::Relation or nil to allow for further methods (such as other scopes) to be called on it.
Reference: https://guides.rubyonrails.org/active_record_querying.html#scopes
That's why is not working as you expected.
I think the best alternative would be to do as Ursus suggested. Create a method, a query object, etc.
You basically want to read an attribute(age) of your model object. For that, you don't need to write any specific method or scope.
Here is what you can do in the place where you have the id and you want to fetch the age for that record:
object = YourClass.find(id)
object.age

Design patterns on initializing an object?

What's the recommended way to handle an object that may not be fully initialized?
e.g. taking the following code (off the top of my head in ruby):
class News
attr_accessor :number
def initialize(site)
#site = site
end
def setup(number)
#number = number
end
def list
puts news_items(#site, #number)
end
end
Clearly if I do something like:
news = News.new("siteA")
news.list
I'm going to run into problems. I'd need to do news.setup(3) before news.list.
But, are there any design patterns around this that I should be aware of?
Should I be creating default values? Or using fixed numbers of arguments to ensure objects are correctly initialized?
Or am I simply worrying too much about the small stuff here.
Should I be creating default values?
Does it make sense to set a default? If so this is a perfectly valid approach IMHO
Or using fixed numbers of arguments to ensure objects are correctly initialized?
You should ensure that your objects cannot be constructed in an invalid state, this will make your's and other users of your code much simpler.
in your example not initializing number in some way is a problem, and this method is an example of temporal coupling. You should avoid this, and the two ways you suggested are ways to do this. Alternatively you can have another object or static method responsible for building your object in a valid state instead
If you do have an object which in not fully initialised then any invalid methods should produce appropriate and descriptive exceptions which let the users know that they are using the code incorrectly, and gives examples of the correct usage patterns.
In c# InvalidStateException is usually appropriate and similar exceptions exist in Java. Ruby is beyond my pay grade unfortunately :)

How to manually instantiate ActiveRecord truncated objects?

Working with ActiveRecord and JRuby, I try to invoke a stored procedure on a Database. Using the underlying Java Library I reached a point where I have a hash with the columns specified in the select.
Now I'd like to use this hash to have ActiveRecord models, but I'd like them to look like if I did a classic Model.select(columns).all (with only the columns values, errors when trying to reach the other ones and readonly).
There must be something inside of AR to do this but I can't find anything and all my search leads to all the basic "fetch" tutorials ...
OK so I kept digging in Rails code and figured out my answer was the instantiate method.
The idea is if you are inside a model called MyModel and do this
object = instantiate(value1: 1, value2: 'ok')
you will have an instance of the MyModel class with theses attributes defined. If the model is supposed to have more columns, they are not defined. The object is readonly.

Rails 3: Best location for reusable function called aftersave

My app has points / awards that can be achieved after a number of actions take place, which generally trigger when a record has been added (completing a mission, etc.)
I wrote a fairly involved function that audits all of the data, and awards the proper amount of points, user ranking, etc. This needs to be called after each one of these records is saved.
I know I can use Observers to call the function after a number of model saves, but I am unclear on where exactly the put said function, and how to call it.
Much appreciated in advance!
Observers usually go in app/models/. You can automatically generate an observer class for a model with the command rails generate observer YourModel. This will generate the file `app/models/your_model_observer.rb'.
By the way, aftersave is not a very descriptive method name. If it does a lot of things it's better to break it up into several methods each of which do one thing, and give each a descriptive name, e.g.:
class YourModelObserver < ActiveRecord::Observer
def after_save your_model_instance
calculate_points your_model_instance
assign_awards your_model_instance
end
private
def calculate_points inst
# ...
end
def assign_awards inst
# ...
end
end

Searching through a collection of indexed model objects using Thinking Sphinx

I have a Technique model which belongs_to User and is indexed by Thinking Sphinx.
I also have a method in my model that returns an array of Technique objects:
def possible_children(user)
user.techniques - (self.children + [self])
end
This just takes the techniques that a user has, subtracts out those of the techniques that are already the children of the 'self' technique object, along with 'self' itself, and returns the remaining technique objects.
Then in a controller I instantiate a collection of possible children like so:
#possible_children = #technique.possible_children(current_user).search params[:search]
This returns an "undefined method 'search' for #"
Not sure if this is relevant but the controller this takes place in is not the TechniquesController.
What I am trying to do is search an arbitrary collection returned by a Model method.
Any ideas?
Let me know if I need to provide more information. Thank you.
I'm afraid this isn't possible with Thinking Sphinx - at least, not that simply. What you could do is use the objects you want to search across, grab their ids, and use that in a filter:
possible_children = #technique.possible_children(current_user)
Technique.search params[:search],
:with => {:sphinx_internal_id => possible_children.collect(&:id)}
Sphinx has its own id, but the primary key from the database is stored as the attribute sphinx_internal_id by Thinking Sphinx.