Is there a better alternative to this Ruby idiom? - ruby-on-rails

I'm finding myself writing this bit of code in my controllers a lot:
params[:task][:completed_at] = Time.parse(params[:task][:completed_at]) if params[:task][:completed_at]
Don't get hung up on what I'm doing here specifically, because the reasons change every time; but there are many circumstances where I need to check for a value in params and change it before handing it off to create or update_attributes.
Repeating params[:task][:completed_at] three times feels very bad. Is there a better way to do this?

One way to shorten this slightly is:
if c = params[:task][:completed_at]
params[:task][:completed_at] = Time.parse(c)
end
Or, you might prefer this:
params[:task][:completed_at] &&= Time.parse(params[:task][:completed_at])
In the second case, the assignment will only happen if the left side is "truthy".

I suppose you could consider doing something like this.
Implement #to_time on String and NilClass, perhaps in a extensions.rb (as recommended in Ruby Best Practices, e.g.
require 'time'
class String
def to_time
Time.parse(self) # add error/exception handling to taste
end
end
class NilClass
def to_time
nil
end
end
Then you can just call params[:task][:created_at].to_time and the duplication is gone.
I'm not at all sure that this necessarily constitutes "best practice", but IMHO it meets the objective of the question...

I am not incredibly familiar with Ruby, but since it has Perl roots, there may be a construct that allows you to write it like this:
$_ = Time->parse($_) for params[:task][:completed_at] || ();
basically exploiting the for loop to create an alias to the variable, if it exists
maybe something like:
(params[:task][:completed_at] || ()).each { |i| i = Time.parse(i) }
edit:
I see that Ruby has an alias keyword. I am not familiar enough with it to give a Ruby example, but in Perl, the above could also be written:
local *_ = \$params[$task][$completed_at];
$_ = Time->parse($_) if defined;
which specifies that $_ will be an alias for $params[$task][$completed_at]
I tried playing around with it breifly in Ruby, but didn't see a way to alias an identifier, just global variables.

Related

What is a simple / elegant way in Rails to process code only if a variable is not a Hash or String?

To check what params[:_search] is and make the good transform, I am doing :
_search = if params[:_search].is_a?(Hash)
params[:_search]
else
CGI::parse(params[:_search].to_s)
end
end
Recently I monkey path Object and wrote this part of code :
class Object
def transform_unless_kind_of(klass)
(!self.is_a?(klass) ? yield(self) : self)
end
end
params[:_search].transform_unless_kind_of(Hash) { |_self| GCI::parse(_self.to_s) }
Is there a more elegant/native way to process code only if an object is not a wanted kind ?
I think you question should be extended with details 'why there are different types coming?' and 'is there a way to avoid it?'.
Generally monkey patches should be avoided as they are usually tricky to understand.
Here you need to conditionally modify argument, and easiest and cleanest way to do it is to write a conditional statement. Further improvement depends on how often you'll need to use it in other places, how extendable it should be, etc
To use less lines you can use ternary operator, or line modifiers:
_search = params[:_search]
_search = CGI.parse(_search) unless _search.is_a?(Hash)
I tend to convert if/else structures into early returns. Something like this:
def search
_search = ensure_parsed(params[:search])
...
end
private
def ensure_parsed(search)
return search if search.is_a?(Hash)
CGI::parse(search.to_s)
end
You can even put ensure_parsed in a concern or something, and your controller code will be even cleaner and monkey-patch free.

Use the instance method in group_by

I don't really like how it looks method all especially that part
.group_by{|x| x.week_number}.values as this part can be replaced?
week_number is the instance method in UserUpdate model.
user_updates_controller.rb
def all
#weekly_updates = current_user.user_updates.group_by{|x| x.week_number}.values
end
user_update.rb
def week_number
(((created_at.utc) - user.first_program_started_at.utc.beginning_of_day) / 86400 / 7).ceil.to_i || 1
end
Alright, I believe I understand better now, but let me know if this is still not quite what you want.
I believe in your case, you can simply do this:
#weekly_updates = current_user.user_updates.group_by(&:week_number).values
The &:week_number notation is a shorthand for creating a proc, and will effectively invoke the week_number method on the object passed in (in this case a UserUpdate object). Ultimately, you should see exactly the same result. There wouldn't be any performance difference, so its mainly just pretty-fying your code.
Check out this question and the answers for lots of examples and explanations on that &: notation.

remote code execution in ruby with constantize

I'm trying to wrap my head around remote code execution vulnerabilities in ruby/rails when contantize is used.
I understand that being able to provide any class name to the server could be potentially dangerous, but I'm wondering if this by itself is dangerous.
for example, if a rails controller code looks something like this (i.e. executes a hardcoded method on the instantiated object):
klass = params[:class].classify.constantize
klass.do_something_with_id(params[:id]) if klass.respond_to?('do_something_with_id')
Is this code vulnerable? Or only in combination with being able to also specify the method to be called on the class?
Turning a string into a constant isn't dangerous in itself, but how that constant is used is potentially dangerous (i.e. the method that is then called).
If you really need to do this, then it's probably best to provide a list of classes that are allowed. E.g.
klass = params[:class].classify
if %w(Class1 Class2 Class3).include? klass
klass.constantize.do_something_with_id(params[:id])
else
raise 'Forbidden'
end
However it's done, it helps you to sleep at night to know that the input is considerably limited.
Update
Another way of controlling the creation, which is more explicit but also more verbose, is to use a case statement:
def create_klass(option)
case option
when "option1"
Class1
when "option2"
Class2
when "option3"
Class3
else
raise "Unknown option"
end
end
This way, you don't need to expose the internals of your system to the client. If there are many options, then you could use a hash with options mapping to classes.

how do I declare const variables in ruby/rails

I would like to be a better code.. making my code cleaner and more readable.
One thing I've seen in c/c++ is the use of const on local variables. I think there is a lot of value in telling the reader of my code that once the variable is set, it is not changed in the function/method.
I'm wondering.... is there a way to indicate a local variable is const?
#for example
sql = "select * from table1 where status = #{iStatusId}"
connection.execute(sql)
sql will not change in this method. Can I mark it so?
Ok.. that's a bad example, but I think the point is made... I hope :)
EDIT:
I added a bit of dynamic to the sql.. the iStatusId bit is a parameter passed into the method.
EDIT2:
I did google this... all articles talk of magic number replacement. That's a no brainer and what I'd consider a MACRO. CONTANTS are easy... I'm looking for const.. they are very different things.
It would be easy enough to cook up the logic you are describing, by using something like this:
class Thing
def unchangeable_attribute
#unchangeable_attribute
end
def unchangeable_attribute=(value)
raise SomeError if #unchangeable_attribute
#unchangeable_attribute = value
end
end
I tend to agree with Dave Newton, though, as I have a hard time imagining a great use case for something like this...
What about the obj.freeze method defined on Ruby objects? From the docs:
Prevents further modifications to obj. A RuntimeError will be raised if modification is attempted. There is no way to unfreeze a frozen object.
Constants just begin with a capital letter, so the following will work just fine:
MY_CONSTANT = 1234
However, overwriting a constant is possible, although it will issue a warning.
Const's in ruby aren't strictly logical. As "all" variables are really just masked pointers to objects, casting them to the c paradigm of const doesn't make sense.
It would be equal to
const void * value = 'bla';
You could still change value[1].
I'd love to be proven wrong here.

How would I use Procs in Ruby on Rails?

There are a few helpers I am using in my project, which I just thought that I could maybe treat as Procs, as they do very specific tasks and can be used by very different components.
I've used Procs in small Ruby projects, mainly when learning the language, and I thought that this would be a good occasion to put them to use.
My question is, where would I put the Procs in the Rails folder structure? Are there any guidelines or reccomdendations for this? Is it considered good practice?
I am a bit puzzled what the advantage would be of using Procs over using simple methods? So if you could give some examples, that would be nice.
Anyways: since Procs can be stored in a variable, I would declare a module inside the lib folder, and define the procs as variables, constants, or methods returning the proc. Something like this
module ProcContainer
def proc_1(factor)
Proc.new { |n| n*factor }
end
PROC_2 = Proc.new { |n| 2 * n }
end
which would be used as
gen_proc = ProcContainer.proc_1(6)
result = gen_proc(3)
other_proc = ProcContainer.PROC_2(4)
The advantage of the method is obvious i guess, since it will return a new Proc object every time it is called, while the constant is only evaluated once.
(of course you should change the naming to something more appropriate)
Ruby has amazing syntax for blocks, so we tend to favor them over explicitly making procs. The downside of blocks is that they need to be executed immediately when the called method yields to them (procs don't have that limitation). That is in place for performance reasons, but you can easily package up a block as a proc, and store it somewhere else for later, or pass it down to another method. So even though you are probably using procs every day, you don't really realize it, because your interface to them is through the block syntax.

Resources