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
Related
Suppose I have a class in, say, Python e.g.:
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
And then I start adding a lot of convenience methods e.g.:
class Date:
# ...
def num_days_in_month(self):
pass
def first_day_of_month(self):
pass
def last_day_of_month(self):
pass
# ...
def next_new_moon(self):
pass
What is the verb for the thing I am doing, and what is the noun for the stuff I am adding to the class, and what is an adjective for a class with a lot of it. I can only think of bad/overly verbose ways of describing this:
The stuff I am adding is "syntactic sugar" and I am "sugaring" or "sweetening" the class, and it makes the class "sweet"/"sugared"
The stuff I am adding is "tooling" and I am "tooling" the class, and it makes that class "tooled'
The things I am adding are "methods" and I am "methoding" the class, and it makes the class ???
The things I am adding are "convenience methods" and I am "conveniencing" the class, and it makes the class "convenienced"
What's the best way?
I don't want just a general world like "developing." I also don't just want a noun, I also want a corresponding single concise verb to describe the act of adding these things (so not "adding convenience methods"). I want to be able to use these in sentences like:
"When designing a class, one important decision to make is how much ____________ to add. On the one hand, you can waste a lot of time __________ing your class and end up with big piles of code that will never get called. On the other, a class that is not ________ enough will require more code to be written by its users.
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 :)
Let's say I have this setup in Ruby on Rails:
class A < ActiveRecord::Base
after_create :perform_some_action
#...
private
def perform_some_action
if some_condition_met?
#take some action
end
end
Should I add something to the method name, perform_some_action, to indicate that it depends on a condition?
Definitely, yes, because your condition check is inside the method.
This way, when calling your method from outside, the code will look self-explanatory. If not, when you read your call it will give you the impression that take_some_action is performed whithout any condition.
However, if 37 conditions are required inside your method, perhaps it's the signal that this method should be split up in several smaller ones.
I'm new to OOP and I'm in the following situation: I have something like a report "Engine" that is used for several reports, the only thing needed is the path of a config file.
I'll code in Python, but this is an agnostic question.So, I have the following two approaches
A) class ReportEngine is an abstract class that has everything needed BUT the path for the config file. This way you just have to instantiate the ReportX class
class ReportEngine(object):
...
class Report1(ReportEngine):
_config_path = '...'
class Report2(ReportEngine):
_config_path = '...'
report_1 = Report1()
B) class ReportEngine can be instantiated passing the config file path
class ReportEngine(object):
def __init__(self, config_path):
self._config_path = config_path
...
report_1 = ReportEngine(config_path="/files/...")
Which approach is the right one? In case it matters, the report object would be inserted in another class, using composition.
IMHO the A) approach is better if you need to implement report engines that are different from each other. If your reports are populated using different logic, follow this approach.
But if the only difference among your report engines is the _config_path i think that B) approach is the right one for you. Obviosly, this way you'll have a shared logic to build every report, regardless the report type.
Generally spoken, put everything which every Report has, in the superclass. Put specific things in the subclasses.
So in your case, put the _config_path in the superclass ReportEngine like in B) (since every Report has a _config_path), but instanciate specific Reports like in A), whereas every Report can set its own path.
I don't know Python, but did a quick search for the proper syntax for Python 3.0+, I hope it makes sense:
class ReportEngine(object):
def __init__(self, config_path):
self._config_path = config_path
def printPath(self):
print self._config_path
...
class Report1(ReportEngine):
def __init__(self):
super().__init__('/files/report1/...')
Then a
reportObj = Report1()
reportObj.printPath()
should print
'/files/report1/...'
Basically the main difference is that approach A is more flexible than B(not mutual change in one report does not influence other reports), while B is simpler and clearer (shows exactly where the difference is) but a change affecting one report type would require more work. If you are pretty sure the reports won't change in time - go with B, if you feel like the differences will not be common in the future - go with A.
I need to store an encoding-state value on a video while it's been encoded
I have a video object. While the video is being encoded it needs to lock edits on its comments.
The video therefore needs to store its current encoding state (is it happening yes/no?) and allow child comments to query that property.
Please note
I know that there are better ways to solve this particular problem. I actually need to solve a slightly different problem however I felt the nuances of it would confuse the question so I've chosen this one instead. My question is specifically around the nuances of isntance variables and not how to better-solve this encoding problem (which obviously needs a queue).
class Video
has_many :comments
after_initialize do
#encoding_in_process = false
end
def encode
#encoding_in_process = true
...
#encoding_in_process = false
end
def encoding_in_process?
#encoding_in_process
end
end
class Comment
belongs_to :video
before_update
raise "locked" if video.encoding_in_process?
end
...
end
As you can see, each video instance is storing an instance variable #encoding_in_process which is used to determine whether a comment can be updated.
The problem
There is a danger there will be multiple in-memory instances of the same video each with different values for #encoding_in_process.
e.g.
bieber_video = Video.find_all_by_artist('Bieber').last
bieber_video.encode
# assume this takes a while...
bieber_video.encoding_in_process?
# => true
bieber_copy = Video.find_by_id bieber_video.id
bieber_copy.encoding_in_process?
# => false
# Each ActiveRecord objects refer to the same Bieber video
bieber_copy.id == bieber_video.id
# => true
# ...however they refer to different objects in memory:
puts bieber_video
#<Video:0x00000105a9e948>
puts bieber_copy
#<Video:0x00000105a11111>
# and hence each instance has a different version of commenting_locked?
# bieber_video.encoding_in_process? != bieber_copy.encoding_in_process?
The question
Given that the same database row might generate two different in-memory instances, what is a safe way to store transient non-database-backed information about those instances?
EDIT
The actual problem I'm trying to solve is setting a flag on an object when destroy is initiated such that its child objects can determine whether or not they're eligible to be destroyed themselves.
It's therefore a very instantaneous problem and not suitable for backing into the database. I used this video example because I thought it was a bit clearer however I may have simply muddied the waters.
THE SOLUTION (courtesy of one of the answers below
#Alex D's suggestion did solve the problem but to add further clarity to this for anyone wanting to repeat, the actual code was this:
class Video
# set a class variable containing an array of all videos
# which are currently being encoded
##ids_of_videos_being_encoded = []
...
def encode
store_encoding_state true
begin
encode()
ensure
# make sure we switch this off after
# encoding finishes or fails
store_encoding_state false
end
end
private
def store_encoding_state encoding_in_progress
if encoding_in_progress
##ids_of_videos_being_encoded.push(id)
else
##ids_of_videos_being_encoded.delete(id)
end
end
def encoding_initiated?
##ids_of_videos_being_encoded.include? id
end
end
The answer to your question depends on whether you may use multiple server processes or not. If you may want to run multiple server processes (which is a good assumption), the problem is not just multiple in-memory ActiveRecord objects representing the same DB row, the problem is multiple objects in different memory spaces.
If you have multiple processes which are somehow collaboratively working with the same data, you must keep that data in a shared store (i.e. a database), and you must flush changes to the store, and refresh your in-memory data as needed. In this case, you cannot rely on transient in-memory data being kept in synchronization (because there is no way it possibly could be).
If constantly writing/reading your transient data to the DB sounds expensive, that's because it is. In general, whenever you have multiple processes (on the same or different servers) working together, you want to design things so each process can grab a chunk of data and work on it for a while without having to communicate with the others. Fine-grained data sharing in a distributed system = bad performance.
If you are sure that you will only ever use a single server process, and you want to simulate the effect of instance variables which are shared between multiple ActiveRecord objects representing the same DB row, keep the data in a hash, keyed by the record ID, and use getters/setters which read/write the hash. If you are doing a lot of this, you can do some metaprogramming "magic" to have the getters/setters automatically generated (a la "attr_accessor"). If you need help writing that metaprogramming code, post a question and I'll answer it.
The video therefore needs to store its current encoding state (is it happening yes/no?) and allow child comments to query that property.
IMO, that's not a good way to do this because of all the synchronization issues that will ensue.
A much better strategy is to start off all videos in an unencoded state, which you store with the video record's metadata. When a video data stream is created, enqueue an encoding task for some worker to carry out. The worker thread will encode the videos, and when it's done, it should update the video's state to encoded.
Now there's no transient state issues; the next time someone tries to comment when the encoding is finished, it'll be done.
Given that the same database row might generate two different in-memory instances, what is a safe way to store transient non-database-backed information about those instances?
If they don't need to be synchronized, then there isn't an issue. If they do need to be synchronized, you run the risk of a race condition. You can also call .reload to refresh an object's state from the database.
And if the data needs to be synchronized like that, then you probably do need to store it. In the video encoding example, you should either store each video's encoded/unencoded state or provide an implicit, authoritative way of knowing whether the video is encoded or not.
Update from the original question:
The actual problem I'm trying to solve is setting a flag on an object when destroy is initiated such that its child objects can determine whether or not they're eligible to be destroyed themselves.
Just use the after_destroy callback to invoke an appropriate method on each child object, and let them determine whether they should be destroyed or not. That will look something like this:
class Video < ActiveRecord::Base
after_destroy :purge_pending_comments!
def purge_pending_comments!
comments.map &:destroy_if_pending
end
end