Send parameter to before_save - ruby-on-rails

I am trying to build an app in the "rails way", so this time instead of retrospectively processing records in the database, I am trying to do them using a before_save method, namely this one:
def make_percentage_from(score)
percent = (score.fdiv(20) * 100)
return percent
end
Every item that comes into the database has a score out of 20, before it gets saved to the database I would like to store this as a percentage, however the issue I am having is that I am unable to send any attribute data via a before_save.
Ideally I'd have
before_save :make_percentage_from(score-to-be-calculated)
How can I do this? Google isn't turning up so much for me and I'm determined not to have to process this data once its stored (providing of course there is another way!)
Thanks and regards
Geoff

The short answer: callbacks never have parameters. It is assumed that callbacks take action on the object or record itself. So anything that you would need as a parameter you would need to store either as an attribute (which is saved to the database) or as an instance variable.

If score and percentage are attributes of Widget:
class Widget < ActiveRecord::Base
before_validates :calculate_score_percentage
validates :percentage, :presence => true
private
def calculate_score_percentage
self.percentage = score.fdiv(20) * 100
end
end
This works because all of your attributes/columns have getter and setter methods automatically defined by ActiveRecord. The reference to score in the calculate_score_percentage method is actually calling the self.score method, which will return the score object/value. We have to use self.percentage explicitly because it would be ambiguous to use percent alone — it could be either defining a local percentage variable, or calling self.percentage=. The default would be the former, which is not what we want in this case.
I'm using before_validates to show that you can use validation still here, which is good for some sanity checking. If you didn't want to do any validation, you could swap it out for before_save without any code changes.

Related

How to mark as outdated an ActiveRecord object attribute?

I have a database trigger that modifies a field on INSERT. Then when I run object.my_attribute it returns nil instead of lets say 42.
If I do object.reload.my_attribute, this is fine. But I don't want to reload the whole object or part of it unless it is necessary. And I believe code shouldn't be concerned when and how an object was created. It should just be correct.
Is it possible to mark that particular attribute as outdated and any attempt to get its value to result in a query that fetches it from database?
For example:
after_save :forget_my_attribute
def forget_my_attribute
forget_field :my_attribute
end
I think it's better to make some service object where field is modified and call it when create the record. CreateModel.call(args) instead of Model.create(args). It will be more clear than database trigger I think
But you can do something like this
after_create_commit :fetch_my_attribute
def fetch_my_attribute
self[:my_attribute] = self.class.find_by(id: id)[:my_attribute]
end
Or more flexible fetch attribute you need dynamically
def fetch_attribute(atr)
self[atr] = self.class.find_by(id: id)[atr]
end
object.fetch_attribute(:my_attribute)

Where should method go (model?, somewhere else)

I've got a method in one of my models which returns data which will be fed into a charting gem.
class MyModel < ActiveRecord::Base
def ownership_data
format_data(item_ownerships.group(:owned).count)
end
end
I need to guarantee that the data return always has 2 values in the result. Something like this:
{ "yes" => 4, "no" => 2 }
In order to do this, I've written another method which is used in the first method:
def format_data(values)
values[false].nil? ? values = values.merge({ "no" => 0 }) : true
values[true].nil? ? values = values.merge({ "yes" => 0 }) : true
return values
end
My question is, where should this method go and how can I unit test it using rspec? I've currently got it in the model, however in trying to test it with rspec, my specs look like this:
let(:values) { { "yes" =>2 } }
it "it will return 2 values" do
result = MyModel.new.format_data(values)
expect(result.keys.count).to eq(2)
end
I'm not too happy about having to instantiate an instance of the model to test this. Any guidance is appreciated.
As AJ mentioned in the comment, we probably need more information to go on to give more specific advice. I'll give some anyway...
If you have a object that isn't necessarily depending on the model itself, you should consider moving it to a plain old ruby object or a service object. Those can live in a separate folder (lib/services) or in your models folder without inheriting from ActiveRecord::Base.
Your method could also be a class method def self.method_name(values, values_2) instead of a instance method (if you don't need specific values from the model).
When it comes to data manipulation for reporting for my projects, I've built specific folder of ruby service objects for those under 'lib/reports' and they take raw data (usually in init method) and return formatted data from a method call (allowing me to have multiple calls if the same data can be formatted in different output options). This makes them decoupled from the model. Also, this makes testing easy (pass in known values in Class.new expect specific values in method outputs.

How to determine a const in some model class?

There is the following model for 'delivery_types' table:
class DeliveryType < ActiveRecord::Base
end
I want to determine a special delivery type, for example, "DELIVERY_BY_TIME", and I want that this const returns DeliveryType.first (I'll put info about this type in my table later). Is it possible? How can I do it? Thanks.
I don't think you can do this, as this is no "real const". What you could do though, is creating a class method, called "by_time", which returns your "by_time" object. I would also not rely on the fact that this is your "first" object. Rather I would use a "find_or_create_by_name("BY_TIME"), which always makes sure you deliver the right object. Combined, something like
def self.by_time
##by_time||= find_or_create_by_name!(name: 'BY_TIME')
end
def by_time?
self == DeliveryType.by_time
end
If you read "Rails anti-patterns", they discourage you from making separate classes for "status" fields. They recommend to just use a string for that in your parent object, with some validators that limit the list of values though...

Update serialized attribute without callbacks in Rails

I am trying to update a serialized attribute with some data in an after_save callback on the same object.
I don't want any callbacks to be triggered, for various reasons (side-effects, infinite loop). The typical way to achieve this would be to use update_column, but unfortunately that doesn't work with serialized attributes.
I am aware that I could put conditionals on my callbacks to avoid them getting called again, but it feels that there should be a form of update_attribute which doesn't trigger callbacks, but still works with serialized attributes.
Any suggestions?
This is what I do
serialize :properties, Hash
def update_property(name, value)
self.properties[name] = value
update_column(:properties, properties)
end
Sharing an example below how you can update serialize attribute without callbacks.
Suppose you have a train object, and there is a serialize attribute in that table called: "running_weekdays", that store on which day that particular train runs.
train = Train.last
train.running_weekdays
=> {"Mon"=>"true", "Tues"=>"true", "Wedn"=>"true", "Thur"=>"true", "Frid"=>"true", "Sat"=>"true", "Sun"=>"true"}
Now suppose you want to set the value for all weekdays as false except 'Monday'
changed_weekdays = {"Mon"=>"true", "Tues"=>"false", "Wedn"=>"false", "Thur"=>"false", "Frid"=>"false", "Sat"=>"false", "Sun"=>"false"}
Now you can update this by using update_column as below:
train.update_column(:running_weekdays, train.class.serialized_attributes['running_weekdays'].dump(changed_weekdays))
Hope this will help.
You need to code the hash before updating the column:
update_column(:properties, self.class.serialized_attributes['properties'].dump(properties))

Something like $ or ## variable, but unique for each user session. Rails

I have array of objects. I can't store it in DB for performance reason. I tried store array in Global ($var) and Class (##var) variables (in controller), but it was the same array for all users. It should be unique for each user session and in each session it should be able to be modified quickly.
I understand that the session[] - is not the best solution. What is the best way?
I'm doing something like this $lines_rules << Processing::rule_creator(...) in every time, when run action in controller.
$lines_rules - it is my array of objects.
Why DB is not right for store $lines_rules? In $lines_rules I store objects with lambda function. When user press button I need call every lambda function with user input and keep result. Then I load new objects into $lines_rules. Thus every request change $lines_rules. I think work with memory is the better way for perfomance.
UPDATE
I use $global_hash [ session[:session_id] ] and this technique for resource saving:
def dead_sessions_killer
ActiveRecord::SessionStore::Session.where(["updated_at < ?", 30.minutes.ago]).each do |session|
$global_hash.delete_if {|key, value| key == session.session_id }
session.delete
end
end
Use a global, but a global hash, keyed by the user's session id.
Store whatever you want in there, although with proper caching, hard to say if this is a great idea.
You could declare the array as a class variable (only 1 # sign), and provide an accessor method.
That is:
class Foo
#some_array
def some_array
#some_array
end
end
From other classes, this lets you call foo_instance.some_array, which keeps the array specific to the user, but allows public access from any class.
You could store the user session in memory with memcache. That would be convenient and fast.
http://awesomerails.wordpress.com/2011/08/23/rails-3-memcached-session-store/

Resources