I'm newbie in ruby on rails, trying to rewrite rails project in PHP, can someone explain me please what next lines of code do?
def payment_types_billing
I18n.t("customer.payment_types_billing").inject({}){|memo,(k,v)|
memo[k.to_s.to_i] = v; memo}.
merge({
PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}",
:amount => number_to_currency(get_account_credit))
})
end
I don't understand part after .inject, if someone can just explain that part in human language, I will be very thankful. :)
These methods work in conjunction. By calling payment_types, the following will occur:
First it grabs a section of the localization yaml (likely in config/locales/en.yml). For more on internationalization/localization, see this!
I18n.t("customer.payment_types_billing")
Then it runs an inject block on the resulting enumerable (in this case a hash), with the intent of returning a newly-formed result (see about .inject here)
.inject({}){|memo,(k,v)| memo[k.to_s.to_i] = v; memo}
The result of this block appears to be a hash whose keys were the keys of the retrieved hash, converted to integers (without knowing the data being accessed, I can't know how this is meant to function).
Addendum:
I suspect the purpose of the above block is to assign integer keys to a new hash (something that is impossible otherwise). Seeing the later steps with the invert, this would mean that the final printed hash will have integer values, not strings.
It then adds two new key value pairs to the hash:
.merge({PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}", :amount => number_to_currency(get_account_credit))})
The first pair has a key equal to ACCOUNTCREDIT, with another value retrieved from the YAML. The second is the key :amount, with the value of "get_account_credit" (presumably a method with a decimal output) converted to currency for the current region.
As we reach the actual content of the payment_types method, the results from above (the newly-formed hash) is a block with a delete condition. If get_account_credit is returning a non-positive number, the ACCOUNTCREDIT keyed pair is deleted
.delete_if {|key, value| (key == PaymentType::ACCOUNTCREDIT && get_account_credit <= 0) }
Finally, the hash is inverted (the keys become the values, and the values become the keys):
.invert
Related
I was reading this SO question: https://stackoverflow.com/a/4402761/2379703 and the last post showing the rails render impl was interesting. It's signature is:
def render(options = {}, locals = {}, &block)
If the first argument is a plain string, it assigns that to options and the rest is interpreted as a hash and assigned to locals. For example:
render('partial_name', key: 'value', key2:, 'value2')
Which results in:
options = "partial_name"
locals = {key: 'value', key2:, 'value2'}
If you just pass key/value pairs, it assumes you passed a single argument of a hash and assigns it all to options and leaves locals empty:
render(partial: 'partial_name', key: 'value', key2", 'value2')
Which results in:
options = {partial: 'partial_name', key: 'value', key2:, 'value2'}
locals = {}
So my question really comes down to is: What is the logic that ruby uses to figure out which parameter gets assigned what when there are multiple optional parameters? Furthermore, it seems that hashes make the answer to this question more interesting since hashes clearly don't need to be delimited with outer {} when passed in as arguments.
A secondary observation, in a test where I used the same signature for a test method like render, and I passed the following in:
render(key: 'value', key2: 'value2', 'string')
And that results in a syntax error:
test_hash_param.rb:15: syntax error, unexpected ')', expecting =>
Why is this? Why doesn't it assign the two key/value pairs to options and sets locals to 'string'?
However this works as I assumed it would:
render({key: 'value', key2: 'value2'}, 'string')
Firstly, you can only pass hash without {} brackets as a last argument to the method. Otherwise it would be much harder for interpreter to find out where does one param ends and another starts.
Having that said, when ruby sees a list of hash-like arguments at the end of argument list, it will always treat it as a single hash. Hence in your example only options have assigned value, as locals has not been passed and default value has been used. There are a lot of issue you can find here here on stackoverflow being result of that. If you need to pass two separate hashes, you need to wrap at least first of them in brackets (and naturally the second one as well if it is not the last argument)
because you're passing hashes without boundaries, ruby has to make decisions about how to interpret them. instead of picking an arbitrary place to divide your key:value pairs into two hashes, it will just group them all in one hash. that means:
render( foo: "bar", hello: "world", "bananas")
gets read as one hash, because it can't tell where you want to end the hash. 'bananas' gets included as a key(because strings can be keys) and pops a syntax error because you didn't assign it a value.
options and locals don't have to be hashes though, because ruby variable types are dynamic. their default value is an empty hash, but if you pass two strings, they'll both be assigned strings. when you pass one string, that gets assigned to options because it is a complete variable. when you pass a symbol/string and a hash rocket though(or a symbol with the colon flipped), you're telling ruby "this is the start of a hash" so it starts looking for key:value pairs. in order to end that hash before the end of your arguments so that you can pass another argument, you have to explicitly tell ruby to stop looking for key:value pairs.
I've got an hash that looks like this
{"1-5 lbs"=>107342.43999999999, "31+ lbs"=>39838.58000000001, "21-30 lbs"=>19036.41, "11-20 lbs"=>39350.95, "6-10 lbs"=>41401.880000000005}
And I'd like to sort it so it looks like this
{"1-5 lbs"=>107342.43999999999, "6-10 lbs"=>41401.880000000005, "11-20 lbs"=>39350.95, "21-30 lbs"=>19036.41, "31+ lbs"=>39838.58000000001 }
The logic is being stored in an instance variable #weight_ranges
You'll need to get into regular expressions to get the value of the first number of each
range.
Hash[(#weight_ranges.sort_by {|key, value| key.scan(/\d+/)[0].to_i})]
To break it down further:
# Sort the weight ranges by the first series of digits found in the key
x = #weight_ranges.sort_by {|key, value| key.scan(/\d+/)[0].to_i}
# Convert each inner Array to a key, value pair in a Hash
x = Hash[x]
You depicted the data as a hash. For a sequence you'll need an array of pairs. Something like this will do it:
list_of_pairs = #weight_ranges.keys.sort_by(&:to_i).map {|k| [k, #weight_ranges[k]]}
This exploits a happy coincidence that to_i stops at the first non-digit it sees.
Correction
I have just learned that in 1.9 Ruby hashes are ordered! So it's easy to adapt:
Hash[#weight_ranges.sort_by{|k,v| k.to_i}]
I'll leave both ideas here, since the first one is still right for Ruby < 1.9.
My goal is to determine whether there is a blank in a hash like this:
{"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
The strings are nested arbitrary times. I do not want to check each of them manually. It would be nice if I can extract all the strings regardless of their depth. Is there a simple way to do that?
Here's an example of how you can extract the values. However you will still have the problem of matching the values to the keys. You can't really have both at the same time.
# Extend the Hash class
class Hash
def recursive_values
self.values.collect { |v|
v.is_a?(Hash) ? v.recursive_values : v
}.flatten
end
end
Usage:
h = {"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
h.recursive_values
=> ["", "", ""]
It will be better if you will use sth like that:
departure_time_slots = {:date => Time.new, :time_start => nil, :time_end => nil}
And when you use keys in Hash it is good practise to using Symbols for keys. (http://www.ruby-doc.org/core-1.9.3/Symbol.html)
No not possible. Because they are totally present in different scope with respect to each other.
For e.g.. keys start and end is totally unknown and masked from the departure_time_slots object in the example above.
One round abut way could be, getting all the values of the hashmap which are of type hashmap again and obtaining their keys recusively.
Fetch keys of departure_time_slots and then from the value list of that map, find all the keys from every value, if that were to be a hashmap. Other than that, I don't think there is another way.
P.S. On side note, see if u can modify your structure to an array where elements can also be arrays, and try and use flatten concept of arrays. :P
I have an AR query that returns a hash of events per month, ordered by month
o.events.group("to_char(date,'MM')").order("to_char(date,'MM')").size()
I'm using numeric months in this query as it was the best way I could find to get things in the correct order, and I also need to do some other manipulations on the hash.
Before display the results, I need to convert the numeric months back to words. I added the following to the end of the query
.each_key{ |key| Date::MONTHNAMES[key] }
But i get
TypeError: can't convert String into Integer.
So i tried
.each_key{ |key| Date::MONTHNAMES[key.to_i] }
But the months remain in numeric form
{"01"=>4, "02"=>3.....
How can i manipulate this hash to get
{"January"=>4, "February"=>3.....
Make a new Hash. If you can't, make a new key in the current hash and delete the original key. You can't simply change a key, since key is a local variable in the block, and changing it in no way impacts the contents of the Hash.
This ? :
def number_to_month(number)
(Time.now.beginning_of_year + number.months).strftime("%B")
end
There are ways to generate a new hash but I think you could just convert the strings to month names in your view right before displaying them. Use the code you already wrote inside the block in your question but put it in your view.
I have a hash that will render my html differently based on a particular variable. The variable is within the hash. So I am wondering how I can pass a hash value to a group by. to sort the rest heres what I am trying, maybe this will explain it better than me wording it.
<% grouped = svcs.group_by { |svc| svc[val[:sorttype]] } %>
val is a multidimensional hash. the first 2 key value pairs sorttype and one other are simple key and value, the 3rd piece (svcs) contains the equivilent of a 2D hash. Which if I manually type the type of sort I want to apply to it for the group by it works ie:
<% grouped = svcs.group_by { |svc| svc[:service_name] } %>
in PHP i know in a similar instance I can pass a variable of some sort to something like this and have it work. I assume such is the case here. However Im not sure how to put the variable in. Cause all the ways Ive tried don't work
It depends a little.
Rails' has a HashWithIndifferentAccess that will not distinguish between string and symbol keys; if you're using one of those, it should work as-is.
If it's not, it depends what the val entries are--if they're strings, convert to a symbol using to_sym, e.g., svc[val[:sorttype].to_sym].