I have sample parameter below:
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"xxxxxxxxxx",
"post" => {
"product_attributes" => {
"name"=>"Ruby",
"product_dtls_attributes" => {
"0"=>{"price"=>"12,333.00"},
"1"=>{"price"=>"111,111.00"}
},
},
"content"=>"Some contents here."
}
Now, the scenario is, I cannot get the price exact value in model.
Instead of:
price = 12,333.00
price = 111,111.00
I get:
price = 12.00
price = 11.00
And now here is what I did in my code:
before_validation(on: :create) do
puts "price = #{self.price}" # I also tried self.price.to_s, but didn't work.
end
UPDATE:
(I am trying do to here is to get the full value and strip the comma).
before_validation(on: :create) do
puts "price = #{self.price.delete(',').to_f}" # I also tried self.price.to_s, but didn't work.
end
Note:
column price is float
The question is, how can I get the exact value of params price.
Thanks!
Looking at the 'price' parameter you provided:
"price"=>"12,333.00"
The problem is with the comma.
For example:
irb(main):003:0> "12,333.00".to_i
=> 12
But you can fix that:
Example:
irb(main):011:0> "12,333.00".tr(",", "_").to_i
=> 12333
The key point is replacing the comma with an underscore. The reason is that 12_333 is the same integer as 12333 (the underscores are ignored). You could just remove the comma with tr(",", "") as well. In this case, you could replace tr with gsub and have the same effect.
By the way, are you aware that your validation method is not doing anything besides printing? Anyway, a before_validation method is not the right approach here because the number will already have been incorrectly converted when the code reaches this point. Instead, you can override the setter on the model:
class MyModel
def price=(new_price)
if new_price.is_a?(String)
new_price = new_price.tr(",", "")
end
super(new_price)
end
end
You can do it like this too:
2.1.1 :002 > "12,333.00".gsub(',', '').to_f
=> 12333.0
This will replace the comma and if you have any decimal value then too it will interpret it:
2.1.1 :003 > "12,333.56".gsub(',', '').to_f
=> 12333.56
The solution I made is to handle it on controller. Iterate the hash then save it. Then it get the proper value which I want to get and save the proper value.
Iterate the following hash and save.
"post" => {
"product_attributes" => {
"name"=>"Ruby",
"product_dtls_attributes" => {
"0"=>{"price"=>"12,333.00"},
"1"=>{"price"=>"111,111.00"}
},
},
"content"=>"Some contents here."
I can't get the full value of price in model because of comma separator. This comma separator and decimal points + decimal places is made by gem.
Price is float, but your data contains a non-numeric character (comma, ","). When the field is converted to a float, parsing likely stops at this character and returns just 12.
I had expected an error to be thrown, though.
I suggest you remove the comma before putting it into the database.
Related
What would be a method that would create these results? I'm not very good at RegEx I'm afraid and couldn't see a relatively straightforward way to achieve this
"100124" => "100124"
"100asdf124" => "100"
"1asdf124" => "1"
"100 124" => "100"
"1 124" => "1"
"100.124" => "100"
"1.124" => "1"
UPDATE
This is NOT the same as extracting numbers from a string! Because I don't wnat to extract ALL numbers from a string, only the FIRST set until some other character or space or punctuation or whatever else that's not a number interrupts it! Added more examples to be clear
Does this method solve your problem?
def numeric_prefix(string)
match = string.match(/^\d+/)
match[0] if match
end
I want it to be able to read in JSON and save it correctly regardless whether the value is 44.5, 44 or 44.99. The price attributes are a decimal format.
The error is happening in the convert_price method. The price in the JSON response can be 44, 44.50 or 44.99. However, I noticed that sometimes the last decimal is cut off, like in the error 44.5.
I'm receiving this error:
undefined method 'match' for float 74.5:Float
My code is:
# read in JSON and create books
def create_item
job_items_url = "https://foobar.com&format=json"
response = open(job_items_url).read.to_s
books = JSON.parse(response)
Book.create(reg_price: convert_price(item['reg_price']),
sale_price: convert_price(item['sale_price']))
end
# format the price
def convert_price(price)
return nil if price.blank? || price.to_f.zero?
price = "#{price}.00" unless price.match(/[,.]\d{2}\z/)
price.delete(',.').to_f / 100
end
You can use number_to_currency without a unit:
>> number_to_currency(45,unit:"")
=> "45.00"
>> number_to_currency(45.5,unit:"")
=> "45.50"
>> number_to_currency(45.55,unit:"")
=> "45.55"
>>
See number_to_currency for more information.
It looks like price is already a Numeric object. Check out sprintf for simple Type-aware padding, for example:
sprintf('%.2f', 44.5) # => "44.50"
# so you should do something like this:
sprintf('%.2f', price.to_f)
A suggestion:
def try_format_currency(price)
sprintf('%.2f', Float(price))
rescue => ex
# log error if you want
nil
end
Use Float() conversion, which can raise, which will clearly express that price is "untrustworthy" input, and might not be a proper number
Express the same thing in the naming of the method
I have a string params, whose value is "1" or "['1','2','3','4']". By using eval method, I can get the result 1 or [1,2,3,4], but I need the result [1] or [1,2,3,4].
params[:city_id] = eval(params[:city_id])
scope :city, -> (params) { params[:city_id].present? ? where(city_id: (params[:city_id].is_a?(String) ? eval(params[:city_id]) : params[:city_id])) : all }
Here i don't want eval.
scope :city, -> (params) { params[:city_id].present? ? where(city_id: params[:city_id]) : all }
params[:city_id] #should be array values e.g [1] or [1,2,3,4] instead of string
Your strings look very close to JSON, so probably the safest thing you can do is parse the string as JSON. In fact:
JSON.parse("1") => 1
JSON.parse('["1","2","3","4"]') => ["1","2","3","4"]
Now your array uses single quotes. So I would suggest you to do:
Array(JSON.parse(string.gsub("'", '"'))).map(&:to_i)
So, replace the single quotes with doubles, parse as JSON, make sure it's wrapped in an array and convert possible strings in the array to integers.
I've left a comment for what would be my preferred approach: it's unusual to get your params through as you are, and the ideal approach would be to address this. Using eval is definitely a no go - there are some big security concerns to doing so (e.g. imagine someone submitting "City.delete_all" as the param).
As a solution to your immediate problem, you can do this using a regex, scanning for digits:
str = "['1','2','3','4']"
str.scan(/\d+/)
# => ["1", "2", "3"]
str = '1'
str.scan(/\d+/)
# => ["1"]
# In your case:
params[:city_id].scan(/\d+/)
In very simple terms, this looks through the given string for any digits that are in there. Here's a simple Regex101 with results / an explanation: https://regex101.com/r/41yw9C/1.
Rails should take care of converting the fields in your subsequent query (where(city_id: params[:city_id])), though if you explictly want an array of integers, you can append the following (thanks #SergioTulentsev):
params[:city_id].scan(/\d+/).map(&:to_i)
# or in a single loop, though slightly less readable:
[].tap { |result| str.scan(/\d+/) { |match| result << match.to_i } }
# => [1, 2, 3, 4]
Hope that's useful, let me know how you get on or if you have any questions.
i am trying to replace some word from a string with gsub function in ruby, but sometimes that works fine and in some cases giving this error? is there any issues with this format
NoMethodError (undefined method `gsub!' for nil:NilClass):
model.rb
class Test < ActiveRecord::Base
NEW = 1
WAY = 2
DELTA = 3
BODY = {
NEW => "replace this ID1",
WAY => "replace this ID2 and ID3",
DELTA => "replace this ID4"
}
end
another_model.rb
class Check < ActiveRecord::Base
Test::BODY[2].gsub!("ID2", self.id).gsub!("ID3", self.name)
end
Ah, I found it! gsub! is a very weird method. Firstly, it replaces the string in place, so it actually modifies your string. Secondly, it returns nil when no substitution has been made. This all sums up to the error you're getting.
The first time you execute that call, it modifies the string assigned to a constant, so it reads as "replace this 3 and name". When you try to run it for a second time, the first gsub will fail to find a string it is looking for so will return nil. The second gsub is then executed on the nil.
On how to solve it - it all depends on what you're trying to achieve. For me, it is somewhat risky to change other class constants (breaks encapsulation). If you just want to get the result without modifying the original string, use gsub (no bang). Or even better, convert those string into a method and use interpolation instead of substitution.
If the strings are just patterns, that should be replaced before get used. A better way would be string Interpolation.
class Test < ActiveRecord::Base
# Here use symbols instead, because symbols themselfs are immutable
# so :way will always equal :way
BODY = {
:new => "replace this %{ID1}",
:way => "replace this %{ID2} and %{ID3}",
:delta => "replace this %{ID4}"
}
end
# HERE you should create another constant based on the
# Test class constants
class Check < ActiveRecord::Base
BODY = {
:way => Test::BODY[:way] % {ID2: self.id, ID3: self.name}
}
# Or you can make it a method
def self.body
Test::BODY[:way] % {ID2: self.id, ID3: self.name}
end
end
this will change every apearance of the hash keys in the string
for example:
str = "%{num1} / %{num1} = 1"
str % {num1: 3} # 3 / 3 = 1
And like #BroiSatse said, you should not change constants of other classes or within the same class itself, at the end they are constants.
My URL is as follows:
http://localhost:3000/movies?ratings[PG-13]=1&commit=Refresh
I'm experimenting evaluating URL params and am unsure why this works the way is does. In my controller I evaluate the parameters and build an array as follows:
In my View I use the following debug statement to see what gets placed into #selected_ratings
=debug(#selected_ratings)
In my controller I have tried two statements.
Test one returns the following, this should work?
#selected_ratings = (params["ratings[PG-13]"].present? ? params["ratings[PG-13]"] : "notworking")
output: notworking
However if I use the following ternary evaluation n my controller:
#selected_ratings = (params["ratings"].present? ? params["ratings"] : "notworking")
output:!map:ActiveSupport::HashWithIndifferentAccess
PG-13: "1"
Why will my evaluation not find the literal params["ratings[PG-13]"]?
Rails parses string parameters of the form a[b]=c as a hash [1] where b is a key and c is its associated value: { :a => { :b => "c" } }.
So the url http://localhost:3000/movies?ratings[PG-13]=1&commit=Refresh will result in the hash:
{ :ratings => { :'PG-13' => "1"}, :commit => "Refresh" }
In your first assignment, you check if params["ratings[PG-13]"] is present, and since it is not, it returns "notworking". In the second case, you check if params["ratings"] is present, and it is, so it returns params["ratings"], which is a hash with the key PG-13 and value "1".
[1] Or rather, a HashWithIndifferentAccess, a special kind of hash that converts symbol and string keys into a single type.