I have a few classes deriving from A.
A does some validation
In the specific case of class B that inherits from A, I'd like to skip the validation.
I'm using active interaction btw
class A < ActiveInteraction::Base
string :s
validate :valid
private
def vaild
#raise something unless s equals "banana"
end
end
class B < A
#do something here to skip A's validation??
def execute
#super cool logic
end
end
Since this is a subclass, you can override the valid method to do something else, or even nothing:
class B < A
def execute
#super cool logic
end
private
def valid
# Do nothing
end
end
You could add a callback for selectively skipping the validation:
class A < ActiveInteraction::Base
string :s
validate :valid, unless: :skip_validation
private
def vaild
# raise something unless s equals "banana"
end
def skip_validation
false
end
end
class B < A
def execute
#super cool logic
end
private
def skip_validation
true # or more fancy logic
end
end
Related
I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end
Sometimes I need not to simply validate smth in my app, but also alter it before/after validating.
I.e.
class Channel < ActiveRecord::Base
validate :validate_url
...
private
def validate_url
url = "rtmp://#{url}" if server_url[0..6] != "rtmp://" #alter cause need this prefix
unless /rtmp:\/\/[a-z0-9]{1,3}\.pscp\.tv:80\/[a-z0-9]\/[a-z0-9]{1,3}\//.match url
errors.add(:url, "...")
end
end
end
or smth like this
class Channel < ActiveRecord::Base
validate :validate_restreams
...
private
def validate_restreams
self.left_restreams = self.left_restreams - self.restreams #to be sure there's no intersections
end
end
But I feel it's not a right place for such things, so I need to know what's the way to do it right?
You can create a custom validator for a rails model. You should make a class, inherit it from ActiveModel::Validator, and define a validate(record) method there, which will add errors to the record. For example:
This is your validator class:
class MyValidator < ActiveModel::Validator
def validate(record)
unless url_valid?(record[:url])
record.errors.add(:url, 'is invalid')
end
end
private
def url_valid?(url)
# validate url and return bool
end
end
And now simply add this to the model:
validates_with MyValidator
I am using Ruby on Rails 3.2.9 and Ruby 1.9.3. I have many model classes implementing similar methods as-like the following:
class ClassName_1 < ActiveRecord::Base
def great_method
self.method_1
end
def method_1 ... end
end
class ClassName_2 < ActiveRecord::Base
def great_method
result_1 = self.method_1
result_2 = self.method_2
result_1 && result_2
end
def method_1 ... end
def method_2 ... end
end
...
class ClassName_N < ActiveRecord::Base
def great_method
result_1 = self.method_1
result_2 = self.method_2
...
result_N = self.method_N
result_1 && result_2 && ... && result_N
end
def method_1 ... end
def method_2 ... end
...
def method_N ... end
end
Those model classes behaves almost the same (not the same) since some of those has an interface with some less or more methods. All methods are differently named (for instance, method_1 could be named bar and method_2 could be named foo), all return true or false, are always the same in each class and there is no relation between them.
What is the proper way to refactor those classes?
Note: At this time I am thinking to refactor classes by including the following module in each one:
module MyModule
def great_method
result_1 = self.respond_to?(:method_1) ? self.method_1 : true
result_2 = self.respond_to?(:method_2) ? self.method_2 : true
...
result_N = self.respond_to?(:method_N) ? self.method_N : true
result_1 && result_2 && ... && result_N
end
end
But I don't know if it is the proper way to accomplish what I am looking for. Furthermore, I am not sure of related advantages and disadvantages...
Looks like you're on the right track. If the method_n methods are unique to your classes then just build the module that you already have into a superclass that each ClassNameN inherits from:
class SuperClassName < ActiveRecord::Base
def great_method
#... what you have in your module
end
end
class ClassNameN < SuperClassName
def method_1 ... end
def method_2 ... end
end
There may be additional ways for you to factor out code depending on what goes on in your method_n methods, but it's impossible to say without more detail.
I would use a metaprogramming solution to clean this up somewhat.
module BetterCode
extend ActiveSupport::Concern
module ClassMethods
def boolean_method(name, *components)
define_method name do
components.all? { |c| send c }
end
end
end
end
And in your models:
class MyModel < ActiveRecord::Base
include BetterCode
boolean_method :great_method, :foo, :bar, :baz, :quux
end
Instances of MyModel will then respond to great_method with a boolean value indicating whether or not foo, bar, baz and quux are all true.
You can abstract out the great_method with something like this:
require 'active_support/concern'
module Greatest
extend ActiveSupport::Concern
module ClassMethods
attr_accessor :num_great_methods
def has_great_methods(n)
#num_great_methods = n
end
end
def great_method
(1..self.class.num_great_methods).each do |n|
return false unless self.__send__("method_#{n}")
end
true
end
end
class ClassName_3
include Greatest
has_great_method 3
# stub out the "method_*" methods
(1..3).each do |n|
define_method "method_#{n}" do
puts "method_#{n}"
true
end
end
end
puts ClassName_1.new.greatest
I have a lots of call to something like this :
User.active[0..5]
Which call :
class User
def active
(an ActiveRelation)
end
end
I am trying to do something like this for performance reasons :
class User
def active[limit]
(an ActiveRelation).limit(limit.to_a.size)
end
end
Unfortunately it doesn't work, any ideas to implement this ?
== EDIT
More cleaner :
class RelationWithLimit < ActiveRecord::Relation
def [] selector
case selector
when Integer
self.offset(selector).limit(1)
when Range
self.offset(selector.to_a[0]).limit(selector.to_a.size)
end
end
end
class ActiveRecord::Base
private
def self.relation #:nodoc:
#relation ||= RelationWithLimit.new(self, arel_table)
finder_needs_type_condition? ? #relation.where(type_condition) : #relation
end
end
You could have your own special subclass of ActiveRelation
class UserReturnRelation < ActiveRecord::Relation
def [] lim
self.limit lim
end
end
class User
def active
# Without knowing exactly what relation you are using
# One way to instantiate the UserReturnRelation for just this call
UserReturnRelation.new(self, arel_table).where("state = active")
end
end
Then User.active[5] should work as expected.
EDIT: Added instantiation info. You may want to look at Base#scoped and Base#relation for more info
Can you try it as params instead of array-indices? eg:
class User
def active(the_limit)
(an ActiveRelation).limit(the_limit)
end
end
User.active(5)
(note: not tested on any actual ActiveRelations...)
You can do it like this:
class User
def active
Limiter.new((an ActiveRelation))
end
class Limiter
def initialize(relation)
#relation = relation
end
def method_missing(method, *arguments, &block)
#relation.send(method, *arguments, &block)
end
def respond_to?(method, include_private = false)
#relation.respond_to?(method, include_private) || super
end
def [](value)
offset = value.to_a.first
limit = value.to_a.last - offset
#relation.offset(offset).limit(limit)
end
end
end
Well, you are defining the method in the wrong class. User.active[0..5] calls the class method active in User and the method [] in whatever class User.active is returning, I'll assume that it is returning an array of users, and Array has already defined the method [] so no worries about that.
You may be getting confused thinking that brackets are some kind of parenthesis for passing arguments to a function while they're not. Try this:
class User
class << self
def [](values)
self.find(values)
end
end
end
So, if you wanna use find with an arrays of ids, you may just use User[1,2,3].
I have this code in my every model.
Class people
def before_validation
#attributes.each do |key,value|
self[key] = nil if value.blank?
end
end
end
Now i want to put my loop in separate module. Like
Module test
def before_validation
#attributes.each do |key,value|
self[key] = nil if value.blank?
end
end
end
And i want to call this before_validation this way
Class people
include test
def before_validation
super
.....Here is my other logic part.....
end
end
Are there any way to do it like that in rails??
You can setup multiple methods to be called by the before_validation callback. So instead of straight up defining the before_validation, you can pass the methods you want to get called before validation.
module Test
def some_test_before_validaiton_method
# do something
end
end
class People < ActiveRecord::Base
include Test
def people_before_validation_foo
#do something else
end
before_validation :some_test_before_validation_method
before_validation :people_before_validaiton_foo
end
You can read more about callbacks here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html