I'm looking for a function that would do the following:
def self.my_find
object = self.first #Whatever
return object.my_check? ? object : nil
end
Something like check:
object.check(&:my_check?)
It should exist, shouldn't it?
Here's my situation:
In a method (controller), I have some nil-able objects and I need to check something on the object to further use it.
In the event of trying to write pseudo-functional code, the logic is first to retrieve the objects, then making actions on the objects and then return those.
If I have a collection, the code would be:
result = get_my_collection_method.select(&:my_check?).map(&:my_action)
There is no issue if the select methods filters all objects, then result will equal [] but the code is still valid.
I find natural to wanting to do the same even if it is not a collection but a single object:
result = get_my_object.check(&:my_check?).try(:my_action)
But the fact that this method doesn't exist tells me there is no monadic transformation between object and array in ruby.
I guess the simplest way to achieve this is that I transform my singleton into a single value array:
result = Array(get_my_object).select(&:my_check?).map(&:my_action).first
I hope this clarifies why I was looking for such method.
But the question remains: does it exist natively? If I write it myself (even at the Object level), this is not standard, I lose the benefits of writing clean code.
While a method like that might seem useful in your particular circumstance, this sort of thing is generally expressed like:
object = self.first
object.my_check? and object
Where it's understood that this method may return false but that's not a logically true value so it's fine.
If you want to write your own method for Object that does this, you're welcome to, but keep in mind the more esoteric and quirky your code is the harder it will be for other people to engage with. Those "other people" might be you in the future when you've forgotten about your unusual core extensions.
Here's a few ways:
def self.my_find
object = self.first # Whatever
# Use ONE of the following examples:
# 1.
object if object.my_check?
# 2.
object.my_check? && object
# 3.
object.my_check? ? object : nil
end
Note that an explicit return is not needed at the end of a method in ruby.
Depending on the context though (I can only speculate, as none was given!), there might be a cleaner way to do this. For example, if this is actually something you're building in a rails application, then one of these examples would be better:
def self.my_find
where(...).find_by(my_check: true)
# Or, where `my_check` is a *SCOPE*!!
my_check.find_by(...)
end
...With the key difference being that you're performing the check in SQL rather than in ruby. This gives better performance.
Related
If we call caller method, we get something like:
prog.rb:3:in `a'
prog.rb:6:in `b'
prog.rb:9:in `c'
This is helpful for humans, but if I wanted to analyze the stack programmatically, not really, as two methods called :a may be entirely unrelated.
Is there any way/method to extract information about the receiver of the methods (like its class or object id) as well? For example:
prog.rb:3:in `Klass#a'
prog.rb:6:in `Modoole#b'
prog.rb:9:in `OtherKlass#c'
Formatting is only an example; this info might be an Array or anything.
I'm trying to emulate this with TracePoint, but forming a separate stack is a bad solution. Is there any Ruby way I missed in the docs?
There's an alternative to Kernel#caller named Kernel#caller_locations, that returns an array of Thread::Backtrace::Location objects. According to the manual, these should in theory be able to give you this information through the #label method.
Returns the label of this frame.
Usually consists of method, class, module, etc names with decoration.
After trying this out however, I need to question the term usually in the docs, because it seems to only return the method name still. Unless usually means it works for you, there seems to be no way of accomplishing this as of now.
Edit:
As per comment, one case that satisfies the condition of usually is when the method call is coming from within a Class or Module body:
class A
def trace
puts caller_locations.first.label
end
end
class B
A.new.trace
end
#=> <class:B>
module C
A.new.trace
end
#=> <module:C>
Is it better to use case/when things or the send method when dynamically calling methods based on user input? "better" based primarily on good coding practices.
input = gets.chomp
case input
when foo
foo
when bar
bar
end
versus
input = gets.chomp #Where hopefully the input would be 'foo' or 'bar'
send(input)
Your wording makes the question incredibly hard to read.
If I understood you correctly, you want to call methods based on user input. One alternative would be to check every possible value and call a method, the other - to use send directly.
First of all, notice that in your first example, you were calling method1 when the user entered foo. If you used send(input) you would have called foo instead. So they are not exactly the same.
You can achieve the same behavior by putting the input->method mapping in a hash like so:
dispatch = {foo: :method1, bar: :method2}
input = gets.chomp.to_sym
send(dispatch[input])
Another thing to note is that send in the original situation would call any method passed. You can instead whitelist the possible methods with the hash above and checking if such value exists:
send(dispatch[input]) if dispatch.key? input
Now to the question of when to use one or the other:
If you have 2, 3, 5 or so possibilities, prefer explicitly listing them. It will be faster, easier to read, easier to do static code analysis and so on.
If you have hundreds and thousands of different methods, prefer send. The costs outweigh the benefits of being DRY.
If the list of allowed methods is generated dynamically, you don't have a choice - use send. Examples:
You want to call methods to a given object and that object is different each time
You want to allow different methods depending on the user's permissions
You want to implement a REPL or some other awesome tool that has extremely dynamic needs
In general, don't use meta programming, unless there is significant gain or you don't have any other choice.
Unless you'd like your user to be able to call any method in the method lookup chain, including private methods which send can invoke, it probably makes sense for you to lock it down and only allow your users some methods.
If you don't specify an object to send to (like in your code above), Ruby will look at self for a method by that name and then use a normal method lookup. In other words self will be the first link in the method lookup chain. If you do specify an object, maybe an object that you create for that purpose for example, another option might be to use the methods like try or respond_to?.
input = gets.chomp
if defined?(input.to_sym)
send(input)
else
puts "No such thing!"
I am iterating through a set of ids and want to return the first object which returns true when a predicate method is called on it. A few lines of code is worth a thousand words:
def applicable_question(question_ids)
ordered_ids(question_ids, order).detect do |question_id|
question = Question.find_by(id: question_id)
return question if question.applicable_for?(self)
end
end
Stripping away the domain terms:
def desired_thing(ids)
ids.detect do |id|
thing = Thing.new(id)
return thing if thing.true?
end
end
Is there a more idiomatic approach here? Specifically, I feel like I am abusing detect. I immediately reached for each and break, but didn't get very far with that approach.
A requirement for this code is for it to not need to instantiate a large array of objects (ActiveRecord subtypes for example) to find the desired thing.
You have:
desired_thing = ids.detect do |id|
thing = Thing.new(id)
return thing if thing.true?
end
If thing is found for which thing.true? is true, detect never returns an element of ids (to be assigned to desired_thing) because it's preempted by return. On the other hand, if the block completes without return being invoked, detect returns nil and assigns that value to desired_thing, but that nil value is of no use in the code that follows. It therefore would be better to just write:
ids.each do |id|
thing = Thing.new(id)
return thing if thing.true?
end
I think you are almost there.
From the find_by_id method, I assume that Question is a class with some kind of ActiveRecord functionality, so I would expect a where method to exist as well.
In that case:
applicable_question = Question.where(id: ids).order(...).detect do |question|
question.applicable_for?(self)
end
will do just fine.
It doesn't feel like abusing detect, you just need to have the correct enumerable set of objects.
Update
If instantiating too many objects is not an acceptable solution, then https://stackoverflow.com/a/29176622/687142 is the way to go. Keep in mind that both approaches have their drawbacks.
Question.where(id: ids).order(...).detect {|q| q.condition? } will do a single query to the database but instantiate too many objects.
ids.each {|id| q = Question.find(id); return q if q.condition? } will perform too many queries to the database but only instantiate one object at a time
The first approach is constantly heavy (memory wise) while the second one's performance depends on the order that you retrieve the records and can become quite expensive as well.
The best choice depends on your data set as well. If you have hundreds of thousands of questions the first approach is out of the question.
Maybe the best choice is to try to order the records in a way that condition? is more likely to be true
What you have isn't far off from what you're trying to do. I would do something like:
desired_thing = ids.detect{ |id| Thing.new(id) }
I'm not sure if you're trying to stay away from SQL methods like find_by_id or where, but I'm sure you could use those too unless you NEED to use detect (or find).
Say I have an object with a method that accesses an object:
def foo
#foo
end
I know I can use send to access that method:
obj.send("foo") # Returns #foo
Is there a straightforward way to do a recursive send to get a parameter on the #foo object, like:
obj.send("foo.bar") # Returns #foo.bar
You can use instance_eval:
obj.instance_eval("foo.bar")
You can even access the instance variable directly:
obj.instance_eval("#foo.bar")
While OP has already accepted an answer using instance_eval(string), I would strongly urge OP to avoid string forms of eval unless absolutely necessary. Eval invokes the ruby compiler -- it's expensive to compute and dangerous to use as it opens a vector for code injection attacks.
As stated there's no need for send at all:
obj.foo.bar
If indeed the names of foo and bar are coming from some non-static calculation, then
obj.send(foo_method).send(bar_method)
is simple and all one needs for this.
If the methods are coming in the form of a dotted string, one can use split and inject to chain the methods:
'foo.bar'.split('.').inject(obj, :send)
Clarifying in response to comments: String eval is one of the riskiest things one can do from a security perspective. If there's any way the string is constructed from user supplied input without incredibly diligent inspection and validation of that input, you should just consider your system owned.
send(method) where method is obtained from user input has risks too, but there's a more limited attack vector. Your user input can cause you to execute any 0-arghument method dispatchable through the receiver. Good practise here would be to always whitelist the methods before dispatching:
VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
send(method)
end
A bit late to the party, but I had to do something similar that had to combine both 'sending' and accessing data from a hash/array in a single call. Basically this allows you to do something like the following
value = obj.send_nested("data.foo['bar'].id")
and under the hood this will do something akin to
obj.send(data).send(foo)['bar'].send(id)
This also works with symbols in the attribute string
value = obj.send_nested('data.foo[:bar][0].id')
which will do something akin to
obj.send(data).send(foo)[:bar][0].send(id)
In the event that you want to use indifferent access you can add that as a parameter as well. E.g.
value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)
Since it's a bit more involved, here is the link to the gist that you can use to add that method to the base Ruby Object. (It also includes the tests so that you can see how it works)
I have a text log from a game with (for example) two types of entries viz. Chat and Event. For the most part they are very similar so I have a LogEntry class defined as so;
class LogEntry < Array
def initialize(str)
super str.split
end
def parse
LogEntry.parse self
end
def LogEntry.parse(entry)
# Processes the elements that are in any Entry
# Figure out whether it's a Chat entry or an Event entry
# Returns an object of type LogChat or LogEvent
end
end
LogChat and LogEvent both extend LogEntry and do further processing relevant to their domain. Everything works as expected;
chat = LogEntry.new("some chat")
event = LogEntry.new("some event")
chat.parse.class # => LogChat
event.parse.class # => LogEvent
Question:
The class method LogEntry.parse essentially returns a parsed entry of the appropriate class. In this context, the parsed entry is the important bit. But we could rename the instance method 'parse' to 'what_type_should_i_be?'. I want the object to act on that information and 'self.become LogEntry.parse(self)'
Right now, to parse an entry, i have to do this;
entry = entry.parse
I want to push this further so that i get the same result with;
entry.parse
I've tried the obvious;
class LogEntry
def parse
self = LogEntry.parse(self)
end
end
Yet I get the error Can't change the value of self. Does anyone know how I should go about achieving this?
Edit:
I have changed my examples because many answers were focusing on the iteration over many entries. Chuck's answer elegantly shows that this situation isn't a problem.
In case this arouses anyone's interest, i've stumbled across Evil Ruby which let's you meddle with `self.class'. There's a nice Orielly article about it called Ruby code that will swallow your soul! I'm looking into it to see if it offers any answers. (Edit: evil.rb is well named! Something that low level doesn't 'seem' suitable for stable/long term distribution.)
I think the fundamental problem is that each is the wrong method here. Either have parse change the object's internal state rather than the object itself, or use map! to replace the objects in the collection with the new versions.
entries.map! {|entry| entry.parse}
will update the objects in the array with the result of calling parse on them, so there's no reason to do weird stuff to self in the parse method.
If you can break out the functionality into different modules, you can mutateextend() self as you like:
class LogEntry
...
def parse! # This mutates self!
case LogEntry.parse!
when :chat
self.extend MyApp::LogChat
when :event
self.extend MyApp::LogEvent
else
raise MyApp::Exception, "waaah"
end
end
end
You don't have to do a clunky case statement with repeated calls to self.extend(), of course, but you get the idea.
for starters, your comments say that LogEntry.parse returns an LogChat or LogEvent object. So you are asking the object to change itself to a different type of object.
It also looks like class methods and instance methods are being confused a little
I am guessing a little but why couldn't you do :
entries.each do |entry|
some_type_log = entry.parse
some_type_of_log.save!
end
EDIT:
sorry, wanted to clarify something. Since you are parsing data that is part of LogEntry, and you want an entry to parse itself, there is no need to pass in any parameters. just keep the parse method parameter-less.
If you know what type of log something is, you can skip a step and parse it on the way in.
chat_entry = LogChat.new(:log => LogEntry)
then make a method called log which is your parser that explicityly handles chat related items.
You've got some string/array/LogEntry confusion here, but assuming you get that worked out, and at the end you still want to have an Array subclass replacing its own contents, you need to use replace:
self.replace(LogEntry.parse(self))