Using Callback Rails with parameter - Ruby - ruby-on-rails

EDITED:
I trying to refactore this:
self.before_create do |obj|
obj.position = self.class.count
end
to this:
self.before_create :set_position
private
def set_position
obj.position = self.class.count
end
But, Its displaying Error:
undefined local variable or method `obj'
How fix this?

class YourClass < AR
before_create :set_position
private
def set_position
self.position = ##count
end
end
The set_position method is instance method, so self is assigned to the instance. The count I believe should be a class variable.

class Something < ActiveRecord::Base
before_create :set_position
protected
def set_position
self.position = self.class.count
end
end
Be aware that if you use class variables you might run into trouble if your server uses multiple processes (##count getting out of sync between them). A functional style queries count from the db.
Note: A lot of this behavior (including properly implemented atomic changes to lists) can be found in the acts_as_list gem, that used to be part of Rails a while back. I suggest using that.

Related

How to raise an error in Ruby on Rails if a function is not called in after_create

Lets say I have a method in my model
class Mod < ...
after_create :update_some_stuff
private
def update_some_stuff
....
end
end
And I want to ensure that "update some stuff" is only called by after create, and to raise an error if it is called in any other context. Is there a way to do this in Ruby on Rails?
maybe you can use a block and avoid doing weird tricks to prevent the method getting called outside the context you want:
after_create do
....
end
It's hacky, and you probably don't need to be testing this because Rails tests this for you, but this should work:
class Mod < ...
after_create :update_some_stuff
private
def update_some_stuff
return unless id_previously_changed?
# do something
end
end
This hooks into previous_changes which is implemented by ActiveModel::Dirty.

No method error for class variable defined with attr_accessor

I want to define methods dynamically using an array of strings.
Here is a simple piece of code that should achieve that.
class SomeClass
attr_accessor :my_array
def initialize(user, record)
#my_array=[]
end
my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
When I instantiate this class in the console, I get the following error
NoMethodError (undefined method `each' for nil:NilClass)
What is wrong with this code and how to make it work. any help highly appreciated :)
Edit 1:
What I ultimately want to achieve is to inherit from SomeClass and override my_array in the child class to dynamically define methods with its attributes like so
class OtherClass < SomeClass
my_array = %w[method1 method2 method3]
# Some mechanism to over write my_array.
end
And then use self.inherited to dynamically define methods in child class.
Is there a good way to achieve this?
In your code, you use an instance variable (#my_array) and an attr_accessor over it, and then try to access my_array from class level (that is, from the body of the class definition, outside of any methods). But instance variables only exist at instance level, so it is not available in the class scope.
One solution (the natural one, and the one which you would probably use in other languages) is to use a class variable: ##my_array. But class variables in ruby are a little problematic, so the best solution would be to make use of class instance variables, like that:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def initialize(user, record)
end
#my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
The syntax is a little tricky, so, if you look that up and it still doesn't makes sense, try just reading about scopes and using a regular class variable with ##.
Edit:
Ok, so, after your edit, it became more clear what you are trying to accomplish. A full working example is like follows:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def awesome_method
puts 'awesome'
end
def self.build!
#my_array.each do |element|
self.define_method("#{element}?".to_sym){ awesome_method }
end
end
end
class ChildClass < SomeClass
#my_array = %w[test little_test]
self.build!
end
child_instance = ChildClass.new
child_instance.test?
>> awesome
child_instance.little_test?
>> awesome
So, I've made some tweaks on SomeClass:
It does not need an initialize method
I tried to use the inherited hook for this problem. It won't ever work, because this hook is called as soon as "ChildClass < SomeClass" is written, and this must be before you can define something like #my_array = %w[test little_test]. So, I have added a self.build! method that must be called in the child instances so that they build their methods from my_array. This is inevitable, but I think it is also good, because it makes more explicit in the subclasses that you are doing something interesting there.
I think you want "define_method", not "alias_method".
awesome_method in passed in a block, which is ruby's way of doing functional programming.
With that done, ChildClass inherits from SomeClass, and it's instances have the dynamically created methods 'test?' and 'little_test?'.
You need to change my_array to class level accessible, in my case class constant.
class SomeClass
DYNAMIC_METHOD_NAMES = %w(method_a method_b method_C).freeze
def initialize(user, record)
end
DYNAMIC_METHOD_NAMES.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end

Is this Rails validation thread-safe

This is running in multiple Sidekiq instances and workers at the same time and it seems that is has generated a couple of issues, like instances getting assigned the "It was alerted recently" error when shouldn't and the opposite.
It is rare, but it is happening, is this the problem or maybe it is something else?
class BrokenModel < ActiveRecord::Base
validates_with BrokenValidator
end
class BrokenValidator < ActiveModel::Validator
def validate record
#record = record
check_alerted
end
private
def check_alerted
if AtomicGlobalAlerted.new(#record).valid?
#record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{#record.errors[:base]}"
end
end
class AtomicGlobalAlerted
include Redis::Objects
attr_accessor :id
def initialize id
#id = id
#fredis = nil
Sidekiq.redis do |redis|
#fredis = FreshRedis.new(redis, freshness: 7.days, granularity: 4.hours)
end
end
def valid?
#fredis.smembers.includes?(#id)
end
end
We were experiencing something similar at work and after A LOT of digging finally figured out what was happening.
The class method validates_with uses one instance of the validator (BrokenValidator) to validate all instances of the class you're trying to validate (BrokenModel). Normally this is fine but you are assigning a variable (#record) and accessing that variable in another method (check_alerted) so other threads are assigning #record while other threads are still trying to check_alerted.
There are two ways you can fix this:
1) Pass record to check_alerted:
class BrokenValidator < ActiveModel::Validator
def validate(record)
check_alerted(record)
end
private
def check_alerted(record)
if AtomicGlobalAlerted.new(record).valid?
record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{record.errors[:base]}"
end
end
2) Use the instance version of validates_with which makes a new validator instance for each model instance you want to validate:
class BrokenModel < ActiveRecord::Base
validate :instance_validators
def instance_validators
validates_with BrokenValidator
end
end
Either solution should work and solve the concurrency problem. Let me know if you experience any other issues.
I believe there are some thread safety issues in rails but we can overcome them by taking necessary precautions.
The local variables, such as your local var, are local to each particular invocation of the method block. If two threads are calling this block at the same time, then each call will get its own local context variable and those won't overlap unless there are shared resources involved: instance variables like (#global_var), static variables (##static_var), globals ($global_var) can cause concurrency problems.
You are using instance variable, just instantiate it every time you are coming to the validate_record method and hopefully your problem will go away like :
def validate record
#record.errors[:base] = []
#record = record
check_alerted
end
For more details you can visit this detailed link
Or try to study about rails configs here : link

Rails model definition using related field

I have a Rails app that is multi-tenant. In the Tenant record, I store codes that are particular to that Tenant.
The following works well (PS - scope doesn't work):
class Worequest < ActiveRecord::Base
acts_as_tenant(:tenant)
def self.closed
where("statuscode_id = ?", ActsAsTenant.current_tenant.request_closed)
end
What I really need is not worequest.statuscode_id but instead worequest.statuscode.position.
I tried this:
def self.closed
self.statuscode.position = ActsAsTenant.current_tenant.request_closed
end
But, that gives this error:
undefined method `statuscode'
Thanks for the help!
Your method is a class method. It means that your code is referring to the class via self object.
def self.closed
self.statuscode.position = ActsAsTenant.current_tenant.request_closed
end
self here is class Worequest and it doesn't have an attribute statuscode.
What are you trying to with self.statuscode.position = ActsAsTenant.current_tenant.request_closed?

Executing Rails virtual attribute setters in order

I have an ActiveRecord model with several virtual attribute setters. I want to build an object but not save it to the database. One setter must execute before the others. How to do?
As a workaround, I build the object in two steps
#other_model = #some_model.build_other_model
#other_model.setup(params[:other_model)
Where setup is:
class OtherModel < ActiveRecord::Base
def setup(other_params)
# execute the important_attribute= setter first
important_attribute = other_params.delete(:important_attribute)
# set the other attributes in whatever order they occur in the params hash
other_params.each { |k,v| self.send("#{k}=",v) }
end
end
This seems to work, but looks kludgy. Is there a better way?
EDIT
per neutrino's suggestion, I added a method to SomeModel:
class SomeModel < ActiveRecord::Base
def build_other_model(other_params)
other_model = OtherModel.new(:some_model=>self)
other_model.setup(other_params)
other_model
end
end
It's a good thing that you have this manipulations done in an OtherModel's method, because you can just call this method and not worry about the order of assignments. So I would leave this part but just call it from a SomeModel's method:
class SomeModel < ActiveRecord::Base
def build_other_model(other_params)
other_model = build_other_model
other_model.setup(other_params)
other_model
end
end
So then you would have
#other_model = #some_model.build_other_model(params[:other_model])
I took your idea of deleting the important attribute first in your setup method, but used alias_chain_method instead to make it more of a transparent process:
def attributes_with_set_important_attribute_first=(attributes = {})
# Make sure not to accidentally blank out the important_attribute when none is passed in
if attributes.symbolize_keys!.include?(:important_attribute)
self.important_attribute = attributes.delete(:important_attribute)
end
self.attributes_without_set_important_attribute_first = attributes
end
alias_method_chain :attributes=, :set_important_attribute_first
This way none of your code should change from the normal Rails style
#other_model = #some_model.other_models.build(params[:other_model])

Resources