Related
I have a class that looks like this:
class ExtractDiffLineInfo
def initialize(*diffs)
#diffs = diffs
end
def all_line_num_starts
result = []
#diffs.each do |diff|
diff.body.lines.each do |diff_line|
if (m = diff_line.match(/\A##\s+\-(\d+),(\d+)\s+\+(\d+),(\d+)\s+##/))
a_start = m[1].to_i
result << a_start + 3
end
end
end
result
end
And when I try to pass an ActiveRecord Collection object, I get an ActiveRecord Collection Proxy error.
I am trying to pass the following #diffs AR Collection:
> #diffs
=> [#<Diff:0x007fc064000510
id: 13645,
body:
"## -1856,7 +1856,7 ## def destroy_associations\n hm_options[k] = options[k] if options.key? k\n end\n \n- has_many name, scope, hm_options, &extension\n+ ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension }\n _reflections[name.to_s].parent_reflection = habtm_reflection\n end\n end",
commit_id: 1,
author: "Ryuta Kamizono",
author_gh_profile: "https://github.com/kamipo",
author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3",
commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_msg:
"Suppress deprecation message to `has_and_belongs_to_many` only once\n\nPassing a class to `has_and_belongs_to_many` show deprecation message\nthree times. It is enough only once.",
gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_date: Mon, 09 Jan 2017 20:00:53 UTC +00:00,
created_at: Fri, 10 Mar 2017 08:33:38 UTC +00:00,
updated_at: Fri, 10 Mar 2017 08:33:38 UTC +00:00,
position: nil,
num_additions: 2,
num_deletions: 2,
num_changes: 4,
starting_line_num: 1859>,
#<Diff:0x007fc063c2f648
id: 13644,
body:
"## -1827,7 +1827,7 ## def has_and_belongs_to_many(name, scope = nil, **options, &extension)\n \n builder = Builder::HasAndBelongsToMany.new name, self, options\n \n- join_model = builder.through_model\n+ join_model = ActiveSupport::Deprecation.silence { builder.through_model }\n \n const_set join_model.name, join_model\n private_constant join_model.name\n",
commit_id: 1,
author: "Ryuta Kamizono",
author_gh_profile: "https://github.com/kamipo",
author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3",
commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_msg:
"Suppress deprecation message to `has_and_belongs_to_many` only once\n\nPassing a class to `has_and_belongs_to_many` show deprecation message\nthree times. It is enough only once.",
gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_date: Mon, 09 Jan 2017 20:00:53 UTC +00:00,
created_at: Fri, 10 Mar 2017 08:33:30 UTC +00:00,
updated_at: Fri, 10 Mar 2017 08:33:30 UTC +00:00,
position: nil,
num_additions: 2,
num_deletions: 2,
num_changes: 4,
starting_line_num: 1830>]
That gets passed like this:
#diff_line_nums = ExtractDiffLineInfo.new(#diffs).all_line_num_starts
This is the error I get:
NoMethodError at /rails/associations-rb/3cd4c7c0ffc3e00004a238304e903a140fcbcd67
undefined method `body' for #<Diff::ActiveRecord_Associations_CollectionProxy:0x007fc0628280c8>
When I checkout the diff object that .body is being called on, I see this:
>> diff
=> #<ActiveRecord::Associations::CollectionProxy [#<Diff id: 13645, body: "## -1856,7 +1856,7 ## def destroy_associations\n ...", commit_id: 1, author: "Ryuta Kamizono", author_gh_profile: "https://github.com/kamipo", author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3", commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3...", commit_msg: "Suppress deprecation message to `has_and_belongs_t...", gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67", commit_date: "2017-01-09 20:00:53", created_at: "2017-03-10 08:33:38", updated_at: "2017-03-10 08:33:38", position: nil, num_additions: 2, num_deletions: 2, num_changes: 4, starting_line_num: 1859>, #<Diff id: 13644, body: "## -1827,7 +1827,7 ## def has_and_belongs_to_many(...", commit_id: 1, author: "Ryuta Kamizono", author_gh_profile: "https://github.com/kamipo", author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3", commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3...", commit_msg: "Suppress deprecation message to `has_and_belongs_t...", gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67", commit_date: "2017-01-09 20:00:53", created_at: "2017-03-10 08:33:30", updated_at: "2017-03-10 08:33:30", position: nil, num_additions: 2, num_deletions: 2, num_changes: 4, starting_line_num: 1830>]>
What's weird is that if I binding.pry into the initialize method at runtime and inspect that diffs argument that is passed in, I see a nested array:
4: def initialize(*diffs)
=> 5: binding.pry
6: #diffs = diffs
7: end
[1] pry(#<ExtractDiffLineInfo>)> diffs
=> [[#<Diff:0x007fc065d635a8
id: 13645,
body:
"## -1856,7 +1856,7 ## def destroy_associations\n hm_options[k] = options[k] if options.key? k\n end\n \n- has_many name, scope, hm_options, &extension\n+ ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension }\n _reflections[name.to_s].parent_reflection = habtm_reflection\n end\n end",
commit_id: 1,
author: "Ryuta Kamizono",
author_gh_profile: "https://github.com/kamipo",
author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3",
commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_msg:
"Suppress deprecation message to `has_and_belongs_to_many` only once\n\nPassing a class to `has_and_belongs_to_many` show deprecation message\nthree times. It is enough only once.",
gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_date: Mon, 09 Jan 2017 20:00:53 UTC +00:00,
created_at: Fri, 10 Mar 2017 08:33:38 UTC +00:00,
updated_at: Fri, 10 Mar 2017 08:33:38 UTC +00:00,
position: nil,
num_additions: 2,
num_deletions: 2,
num_changes: 4,
starting_line_num: 1859>,
#<Diff:0x007fc05bf57c88
id: 13644,
body:
"## -1827,7 +1827,7 ## def has_and_belongs_to_many(name, scope = nil, **options, &extension)\n \n builder = Builder::HasAndBelongsToMany.new name, self, options\n \n- join_model = builder.through_model\n+ join_model = ActiveSupport::Deprecation.silence { builder.through_model }\n \n const_set join_model.name, join_model\n private_constant join_model.name\n",
commit_id: 1,
author: "Ryuta Kamizono",
author_gh_profile: "https://github.com/kamipo",
author_gh_avatar: "https://avatars.githubusercontent.com/u/12642?v=3",
commit_url: "https://github.com/rails/rails/commit/3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_msg:
"Suppress deprecation message to `has_and_belongs_to_many` only once\n\nPassing a class to `has_and_belongs_to_many` show deprecation message\nthree times. It is enough only once.",
gh_sha: "3cd4c7c0ffc3e00004a238304e903a140fcbcd67",
commit_date: Mon, 09 Jan 2017 20:00:53 UTC +00:00,
created_at: Fri, 10 Mar 2017 08:33:30 UTC +00:00,
updated_at: Fri, 10 Mar 2017 08:33:30 UTC +00:00,
position: nil,
num_additions: 2,
num_deletions: 2,
num_changes: 4,
starting_line_num: 1830>]]
What could be causing this and how do I fix it?
It's because of your splat
def initialize(*diffs) # <- this one
#diffs = diffs
end
You can overcome it by doing this, for example:
ExtractDiffLineInfo.new(*#diffs.to_a).all_line_num_starts
Or expand arrays in the initializer
def initialize(*diffs)
#diffs = #diffs.flatten # _should_ work on activerelations, didn't test.
end
If I were you, I would ditch the splat and give up the ability to pass a single diff.
I need to be able to easily manage a single diff though
You could have specialized factory methods. Something like this:
class ExtractDiffLineInfo
def self.for_one_diff(diff)
new([diff])
end
def self.for_many_diffs(diffs)
new(diffs)
end
def initialize(*diffs)
#diffs = diffs
end
end
ExtractDiffLineInfo.for_one_diff(my_diff).all_line_num_starts
ExtractDiffLineInfo.for_many_diffs(#diffs).all_line_num_starts
I have an array bed_times with Time instances in UTC format:
bed_times = [
Time.utc(2015, 12, 10, 5, 58, 24),
Time.utc(2015, 12, 9, 3, 35, 28),
Time.utc(2015, 12, 8, 6, 32, 26),
Time.utc(2015, 12, 7, 1, 43, 28),
Time.utc(2015, 12, 5, 7, 49, 30),
Time.utc(2015, 12, 04, 7, 2, 30)
]
#=> [2015-12-10 05:58:24 UTC,
# 2015-12-09 03:35:28 UTC,
# 2015-12-08 06:32:26 UTC,
# 2015-12-07 01:43:28 UTC,
# 2015-12-05 07:49:30 UTC,
# 2015-12-04 07:02:30 UTC]
I am trying to get the average bedtime, but I'm not getting the correct result
ave = Time.at(bed_times.map(&:to_f).inject(:+) / bed_times.size)
result is
2015-12-07 01:26:57 -0800
which is not correct. Also, I want to then convert the average time to a different time zone
I tried
Time.zone = 'Pacific Time (US & Canada)'
Time.zone.parse(ave.to_s)
2015-12-07 01:26:57 -0800
This is not correct either.
You have to calculate the average on the gap from midnight.
A not elegant (but fast) solution could be:
# Keep only time
bed_times.map! { |bt| Time.parse(bt.split(" ")[1]) }
# calculate the gap from 00:00:00
gap_from_midnight = bed_times.map do |bt|
if bt > Time.parse("12:00:00")
gap = (bt.to_f - Time.parse("24:00:00").to_f)
else
gap = (bt.to_f - Time.parse("00:00:00").to_f)
end
gap.to_i
end
# average in sec
avg_in_sec = gap_from_midnight.inject(:+) / bed_times.size
# average in UTC time zone
avg = Time.at(avg_in_sec).utc # => 1970-01-01 05:26:57 UTC (result for bed_times array)
# average in PST time zone (see note)
avg_pst = Time.parse(avg.to_s).in_time_zone("Pacific Time (US & Canada)") # => Wed, 31 Dec 1969 21:26:57 PST -08:00 (result for bed_times array)
# Keep only time
avg_pst.strftime("%H:%M:%S") # => "21:26:57" (result for bed_times array)
With your bed_times array (with the values as a string)
bed_times = [
"2015-12-10 05:58:24 UTC",
"2015-12-09 03:35:28 UTC",
"2015-12-08 06:32:26 UTC",
"2015-12-07 01:43:28 UTC",
"2015-12-05 07:49:30 UTC",
"2015-12-04 07:02:30 UTC"
]
the average is :
05:26:57 in UTC zone
21:26:57 in PST zone
With another array like this
bed_times = [
"2015-12-10 01:00:00 UTC",
"2015-12-09 23:00:00 UTC",
"2015-10-19 18:00:00 UTC",
]
the average is:
22:00:00 in UTC zone
14:00:00 in PST zone
note: .in_time_zone is a helper from ActiveSupport::TimeWithZone
http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
As I understand, you are given an array arr that contains the UTC times of bedtimes in areas that are on PST time. You wish to compute the average bedtime.
Code
def avg_bedtime(arr)
avg = Time.at(arr.reduce(0) do |t,s|
lt = Time.parse(s).localtime("-08:00")
t + Time.new(2000, 1, (lt.hour < 12) ? 2 : 1, lt.hour, lt.min, lt.sec, "-08:00").to_f
end/arr.size)
"%d:%d:%d" % [avg.hour, avg.min, avg.sec]
end
Example
arr = ["2015-12-10 08:58:24 UTC", "2015-12-09 03:35:28 UTC",
"2015-12-08 06:32:26 UTC", "2015-12-07 01:43:28 UTC",
"2015-12-05 07:49:30 UTC", "2015-12-04 07:02:30 UTC"]
I've changed the first time in this array to make it more interesting.
avg_bedtime(arr)
#=> "21:56:57"
Explanation
Let's begin by converting these strings to time objects:
utc = arr.map { |s| Time.parse(s) }
#=> [2015-12-10 08:58:24 UTC, 2015-12-09 03:35:28 UTC, 2015-12-08 06:32:26 UTC,
# 2015-12-07 01:43:28 UTC, 2015-12-05 07:49:30 UTC, 2015-12-04 07:02:30 UTC]
Recalling that PST is 8 hours later than UTC, we can use Time.localtime to convert to PST:
pst = utc.map { |t| t.localtime("-08:00") }
#=> [2015-12-10 00:58:24 -0800, 2015-12-08 19:35:28 -0800,
# 2015-12-07 22:32:26 -0800, 2015-12-06 17:43:28 -0800,
# 2015-12-04 23:49:30 -0800, 2015-12-03 23:02:30 -0800]
I will refer to bedtimes before noon to be "late" bedtimes and those later in the day to be "early" bedtimes. (This is of course arbitrary. If, for example, some of the individuals are shift workers, this could be a problem.) As you see, the first element of pst is a late bedtime and all others are early bedtimes.
I will now convert these time objects to time objects having the same time of day but a different date. Early bedtime objects will be assigned an arbitrary date (say, January 1, 2000) and late bedtime objects will be one day later (January 2, 2000):
adj = pst.map { |t| Time.new(2000, 1, (t.hour < 12) ? 2 : 1, t.hour, t.min, t.sec, "-08:00") }
#=> [2000-01-02 00:58:24 -0800, 2000-01-01 19:35:28 -0800, 2000-01-01 22:32:26 -0800,
# 2000-01-01 17:43:28 -0800, 2000-01-01 23:49:30 -0800, 2000-01-01 23:02:30 -0800]
We can now convert the time objects to seconds since the epoch:
secs = adj.map { |t| t.to_f }
#=> [946803504.0, 946784128.0, 946794746.0, 946777408.0, 946799370.0, 946796550.0]
compute the average:
avg = secs.reduce(:+)/arr.size
#=> 946792617.6666666
convert back to a time object for the PST zone:
tavg = Time.at(avg)
#=> 2000-01-01 21:56:57 -0800
and, lastly, extract the time of day:
"%d:%d:%d" % [tavg.hour, tavg.min, tavg.sec]
# "21:56:57
I have two hash like
h1 = {
DateTime.new(2015, 7, 1),in_time_zone => 0,
DateTime.new(2015, 7, 2).in_time_zone => 10,
DateTime.new(2015, 7, 4).in_time_zone => 20,
DateTime.new(2015, 7, 5).in_time_zone => 5
}
h2 = {
DateTime.new(2015, 7, 1).in_time_zone => 0,
DateTime.new(2015, 7, 2).in_time_zone => 0,
DateTime.new(2015, 7, 3).in_time_zone => 0
}
I want to merge h1 and h2, don't merge if key already exist, so that will result look like (datetime format with time zone shortened for readability)
result
#=> {
# Wed, 01 Jul 2015 01:00:00 EST +01:00 => 0,
# Thu, 02 Jul 2015 01:00:00 EST +01:00 => 10,
# Fri, 03 Jul 2015 01:00:00 EST +01:00 => 0,
# Sat, 04 Jul 2015 01:00:00 EST +01:00 => 20,
# Sun, 05 Jul 2015 01:00:00 EST +01:00 => 5
# }
I have tried with h1.merge(h2) and h2.merge(h1) but it can be put key and value of h2 to h1.
arr = []
h = h1.merge(h2)
h.each{|k, v| arr.include?(v) ? h.delete(k) : arr << v }
#=> {#<DateTime: 2015-07-01T00:00:00+00:00 ((2457205j,0s,0n),+0s,2299161j)>=>0,
#<DateTime: 2015-07-04T00:00:00+00:00 ((2457208j,0s,0n),+0s,2299161j)>=>20,
#<DateTime: 2015-07-05T00:00:00+00:00 ((2457209j,0s,0n),+0s,2299161j)>=>5}
You will have only three key-value pairs, not 5 as you expect, because hash in Ruby is collection of unique keys and their values.
I'm using:
ranking = Ranking.create()
ranking.send("#{month}=", rank)
ranking.save!
I'd like to append whatever value is in the #{month} column, not replace it. For example, if I am performing:
month = 'january'
ranking.send("#{month}=", 500)
ranking.save!
And then again later on:
month = 'january'
ranking.send("#{month}=", 250)
ranking.save!
The value for the column january for that particular ranking should be 750.
Is this possible with the current ActiveRecord API?
You could do this with increment! method
month = 'january'
ranking.increment!(month, 250)
updated:
to proof comments question (e.g. month = 'jan'):
irb(main):011:0> p.increment!(month, 70)
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "products" SET "jan" = ?, "updated_at" = ? WHERE "product
"."id" = 1 [["jan", 171], ["updated_at", Sun, 06 Oct 2013 04:23:54 UTC +00:00]
(0.0ms) commit transaction
=> true
irb(main):012:0> p
=> #<Product id: 1, name: nil, description: nil, jan: 171, created_at: "2013-10-
06 04:22:50", updated_at: "2013-10-06 04:23:54">
and another case
irb(main):013:0> p.increment!("#{month}", 70)
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "products" SET "jan" = ?, "updated_at" = ? WHERE "products
"."id" = 1 [["jan", 241], ["updated_at", Sun, 06 Oct 2013 04:24:10 UTC +00:00]]
(0.0ms) commit transaction
=> true
irb(main):014:0> p
=> #<Product id: 1, name: nil, description: nil, jan: 241, created_at: "2013-10-
06 04:22:50", updated_at: "2013-10-06 04:24:10">
See the following output:
1.9.3p194 :001 > player = Player.randomize_for_market
=> #<Player id: nil, name: "Gale Bridges", age: 19, energy: 100, attack: 6, defense: 4, stamina: 5, goal_keeping: 3, power: 4, accuracy: 5, speed: 5, short_pass: 5, ball_controll: 4, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :002 > player.save!
(0.2ms) BEGIN
SQL (20.5ms) INSERT INTO "players" ("accuracy", "age", "attack", "ball_controll", "contract_id", "created_at", "defense", "energy", "goal_keeping", "long_pass", "name", "power", "regain_ball", "short_pass", "speed", "stamina", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING "id" [["accuracy", 5], ["age", 19], ["attack", 6], ["ball_controll", 4], ["contract_id", nil], ["created_at", Fri, 29 Jun 2012 04:02:34 UTC +00:00], ["defense", 4], ["energy", 100], ["goal_keeping", 3], ["long_pass", 6], ["name", "Gale Bridges"], ["power", 4], ["regain_ball", 5], ["short_pass", 5], ["speed", 5], ["stamina", 5], ["updated_at", Fri, 29 Jun 2012 04:02:34 UTC +00:00]]
(16.6ms) COMMIT
=> true
1.9.3p194 :003 > YAML::load(YAML::dump(Player.randomize_for_market)).save!
(0.2ms) BEGIN
(0.2ms) COMMIT
=> true
Why this happens and how can I avoid it?
There is no ((before|after)+(save|create|commit)) on the model. I'm using rails 3.2.
Table "public.players"
Column | Type | Modifiers
--------------+-----------------------------+------------------------------------------------------
id | integer | not null default nextval('players_id_seq'::regclass)
name | character varying(255) | not null
age | integer | not null
energy | integer | not null
attack | integer | not null
defense | integer | not null
stamina | integer | not null
goal_keeping | integer | not null
power | integer | not null
accuracy | integer | not null
speed | integer | not null
short_pass | integer | not null
ball_controll | integer | not null
long_pass | integer | not null
regain_ball | integer | not null
contract_id | integer |
created_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
Indexes:
"players_pkey" PRIMARY KEY, btree (id)
Edit: Answering "Why do you expect YAML::load(YAML::dump(Player.randomize_for_market)).save! to do anything?"
Because it serializes a object and recovers it?
example:
1.9.3p194 :006 > p = Player.randomize_for_market
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :007 > p
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :008 > YAML::load(YAML::dump(p))
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
Note that the return of p is the same of the return from YAML::load
This may help to answer your question:
:001 > article = Article.new
#<Article:0x102d16b10> { ... }
:002 > article.persisted?
false
:003 > dumped = YAML::dump(article)
"--- !ruby/object:Article ... "
:004 > loaded = YAML::load(dumped)
#<Article:0x102cf5500> { ... }
:005 > loaded.persisted?
true
Looking into the Rails source code for ActiveRecord::Base#persisted?:
def persisted?
!(new_record? || destroyed?)
end
And for ActiveRecord::Base#new_record?:
def new_record?
#new_record
end
The #new_record instance variable is not saved when you dump the object to Yaml, and therefore it's nil when you load the object from Yaml. So ActiveRecord thinks it's already been persisted to the database and doesn't attempt to save it.
Brandan's answer is very relevant, the object de-serialized from YAML thinks it's already been persisted. Assuming #loaded_obj is the object you loaded from YAML (the object you want to save), try #loaded_obj.dup.save