How to parse the text to get all mentionees? - ruby-on-rails

I would like to parse a text to get array of mentioneess from that text :
class Mentionee
attr_reader :id, :display_name
def initialize(id:, display_name:)
#id = id
#display_name = display_name
end
def self.parse(text)
# ???
end
end
mentionees = Mentionee.parse('[1:John C.] [2: Smith X.] you are awesome!')
mentioneess[0].id # => '1'
mentioneess[0].display_name # => 'John C.'
mentioneess[1].id # => '2'
mentioneess[1].display_name # => 'Smith X.'

I think this would help you.
> '[1:John C.] [2: Smith X.] you are awesome!'.scan(/(?<=\[)(\d+)(?=:\s*([^\]]+))/)
=> [["1", "John C."], ["2", "Smith X."]]

If I have understood you want to parse text passed to method parse
def self.parse(text)
text.scan(/\[(.*?):(.*?)\]/).map do |e|
{id: e[0], display_name: e[1]}
end
end
Will produce:
[
{id: "1", display_name: "John C."},
{id: "2", display_name: "Smith X."}
]
and you will be able to use as you described
mentionees = Mentionee.parse('[1:John C.] [2: Smith X.] you are awesome!')
mentioneess[0][:id] # => 1
mentioneess[0][:display_name] # => 'John C.'
mentioneess[1][:id] # => 2
mentioneess[1][:display_name] # => 'Smith X.'

Related

TypeError no implicit conversion of Symbol into Integer

Hash
data = {
:recordset => {
:row => {
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"}
]
}
},
:#xmlns => "http://localhost/test"
}
Code Used
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
I cannot get the following output:
[{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"}
Error message:
TypeError no implicit conversion of Symbol into Integer
It works correctly in case of multi records
data = {
:recordset => {
:row => [{
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"},
{:name => "Phone1", :value => "1234567890"}
]
}, {
:property => [
{:name => "Code", :value => "C0002"},
{:name => "Customer", :value => "VERDE VINCENT"},
{:name => "Phone1", :value => "9876543210"},
{:name => "Phone2", :value => "2468101214"}
]
}]
},
:#xmlns => "http://localhost/test"
}
Code used
data.keys
#=> [:recordset, :#xmlns]
data[:recordset][:row].count
#=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
#=> [
# {"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}
# ]
In the first case data[:recordset][:row] is not an Array, it's a Hash, so when you iterate it, the hash variable becomes the array:
[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]
In the second case, it's an Array, not a Hash, so when you iterate it, it becomes the hash:
{:property=>[{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}, {:name=>"Phone1", :value=>"1234567890"}]}
You're always assuming it's the second format. You could force it into an array, and then flatten by 1 level to treat both instances the same:
result = [data[:recordset][:row]].flatten(1).each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO"}] # result from example 1
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT",
# "Phone1"=>"9876543210", "Phone2"=>"2468101214"}] # result from example 2
It's tempting to try and use Kernal#Array() instead of [].flatten(1), but you have to remember that Hash implements to_a to return a nested array of keys and values, so Kernal#Array() doesn't work like you'd want it to:
Array(data[:recordset][:row]) # using the first example data
# => [[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]]
You can create an array if it's not an array to normalize the input before processing it.
info = data[:recordset][:row]
info = [info] unless info.is_an? Array
result = info.each_with_object([]) do ....

Rails hash combine by their value

I have a questions list, and I need to separate them. The relationship is
Question_set has_many questions
BookVolume has_many questions
Subject has_many book_volumes
Publisher has_many subjects
Section has_many :questions
Now I only put questions and their relative model id, name into hash inside an array.
data = []
question_set.questions.each do |q|
data << {publisher: {id: q.publisher.id, name: q.publisher.name}, subject: {id: q.book_volume.subject.id, name: q.book_volume.subject.name}, volume: {id: q.book_volume_id, name: q.book_volume.name}, chapter: [{id: q.section_id, name: q.section.name}]}
end
Therefore, the data basically will be
>>data
[
{
:publisher => {
:id => 96,
:name => "P1"
},
:subject => {
:id => 233,
:name => "S1"
},
:volume => {
:id => 1136,
:name => "V1"
},
:chapter => [
{
:id => 16155,
:name => "C1"
}
]
},
{
:publisher => {
:id => 96,
:name => "P1"
},
:subject => {
:id => 233,
:name => "S1"
},
:volume => {
:id => 1136,
:name => "V1"
},
:chapter => [
{
:id => 16158,
:name => "C2"
}
]
}
]
However, I want the chapter to be combined if they got the same publisher, subject and volume
So, in this case, it will be
>>data
[
{
:publisher => {
:id => 96,
:name => "P1"
},
:subject => {
:id => 233,
:name => "S1"
},
:volume => {
:id => 1136,
:name => "V1"
},
:chapter => [
{
:id => 16155,
:name => "C2"
},
{
:id => 16158,
:name => "C2"
}
]
}
]
Code
def group_em(data)
data.group_by { |h| [h[:publisher], h[:subject], h[:volume]] }.
map do |k,v|
h = { publisher: k[0], subject: k[1], volume: k[2] }
h.update(chapters: v.each_with_object([]) { |f,a|
a << f[:chapter] }.flatten)
end
end
Example
Let data equal the array of hashes (the first array above).
group_em(data)
#=> [{:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapters=>[{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
# }
# ]
Here data contains only two hashes and those hashes have the same values for the keys :publisher, :subject and :volume. This code allows the array to have any number of hashes, and will group them by an array of the values of those three keys, producing one hash for each of those groups. Moreover, the values of the key :chapters are arrays containing a single hash, but this code permits that array to contain multiple hashes. (If that array will always have exactly one hash, consider making the value of :chapters the hash itself rather than an array containing that hash.)
Explanation
See Enumerable#group_by and Hash#update (aka Hash#merge!).
The steps are as follows.
h = data.group_by { |h| [h[:publisher], h[:subject], h[:volume]] }
#=> {
# [{:id=>96, :name=>"P1"},
# {:id=>233, :name=>"S1"},
# {:id=>1136, :name=>"V1"}
# ]=>[{:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapter=>[{:id=>16155, :name=>"C1"}]
# },
# {:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapter=>[{:id=>16158, :name=>"C2"}]
# }
# ]
# }
The first key-value pair is passed to map's block and the block variables are assigned.
k,v = h.first
#=> [[{:id=>96, :name=>"P1"}, {:id=>233, :name=>"S1"}, {:id=>1136, :name=>"V1"}],
# [{:publisher=>{:id=>96, :name=>"P1"}, :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"}, :chapter=>[{:id=>16155, :name=>"C1"}]},
# {:publisher=>{:id=>96, :name=>"P1"}, :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"}, :chapter=>[{:id=>16158, :name=>"C2"}]}]]
k #=> [{:id=>96, :name=>"P1"}, {:id=>233, :name=>"S1"}, {:id=>1136, :name=>"V1"}]
v #=> [{:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapter=>[{:id=>16155, :name=>"C1"}]},
# {:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapter=>[{:id=>16158, :name=>"C2"}]}]
and the block calculation is performed.
h = { publisher: k[0], subject: k[1], volume: k[2] }
#=> {:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"}
# }
a = v.each_with_object([]) { |f,a| a << f[:chapter] }
#=> [[{:id=>16155, :name=>"C1"}], [{:id=>16158, :name=>"C2"}]]
b = a.flatten
#=> [{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
h.update(chapters: b)
#=> {:publisher=>{:id=>96, :name=>"P1"},
# :subject=>{:id=>233, :name=>"S1"},
# :volume=>{:id=>1136, :name=>"V1"},
# :chapters=>[{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
# }
Hash#merge could be used in place of Hash#update.
How about:
data = {}
question_set.questions.each do |q|
key = "#{q.publisher.id}:#{q.book_volume.subject.id}:#{q.book_volume_id}"
if data[key].present?
data[key][:chapter] << {id: q.section_id, name: q.section.name}
else
data[key] = {publisher: {id: q.publisher.id, name: q.publisher.name}, subject: {id: q.book_volume.subject.id, name: q.book_volume.subject.name}, volume: {id: q.book_volume_id, name: q.book_volume.name}, chapter: [{id: q.section_id, name: q.section.name}]}
end
end
result = data.values
use the combination of publisher'id, subject'id and volume'id as a unique key to combine your data.

How to restructure results from database

I've got a model User which has id, name, surname, role_id, permission_id
Here is few user entries :
User => {:name => 'Bob', :surname => 'surnmae', :role_id => 1, :permission_id = 2}
User => {:name => 'Alice', :surname => 'strange', :role_id => 1, :permission_id = 3}
User => {:name => 'Ted', :surname => 'Teddy', :role_id => 2, :permission_id = 3}
Now I need to group them first by role_id and then by permission_id, here is what I mean :
Category.select([:name, :role_id, :permission_id]).group_by(&:role_id)
Produces :
{1 =>
[#<User name: "Bob", role_id: 1, permission_id: 2>,
#<User name: "Alice", role_id: 1, permission_id: 3>]
2 => [#<User name: "Ted", role_id: 2, permission_id: 3>]
}
Which is close enough but I need them grouped with permission_id as well so it would look like this :
{:role_id => {:permision_id => [Array of users grouped by this criteria]}
}
It is bit more tricky than it seems. It is better to write it into multiple steps. However, the following one-liner will work:
Hash[ User.all.group_by(&:role_id).collect{|role, grp| [role, grp.group_by(&:permission_id)]} ]
Output will be the following (which is probably what you are looking for):
{1=>
{2=>[{:name=>"Bob", :surname=>"surnmae", :role_id=>1, :permission_id=>2}],
3=>[{:name=>"Alice", :surname=>"strange", :role_id=>1, :permission_id=>3}]},
2=>{3=>[{:name=>"Ted", :surname=>"Teddy", :role_id=>2, :permission_id=>3}]}}
Same logic, but simpler to comprehend:
output={}
User.all.group_by(&:role_id).each{|role, grp| output[role]= grp.group_by(&:permission_id)}
# output has required goruping
maybe something like
Category.select([:name, :role_id, :permission_id]).group_by(&:role_id).map{|role| role.group_by(&:permission_id)}
Category.select([:name, :role_id, :permission_id]).group_by { |category| [category.role_id, category.permission_id] }
Edit: Nevermind, this doesn't provide quite the formatting you're looking for.

Ruby on Rails: I'm trying to recursively generate a hash, but I get {...} where there is supposed to be another depth of data

This is what I've been getting:
{:user=>{:employees=>{...}, :login=>"dernalia", :id=>1, :role=>2}}
What is generating the hash:
def management_tree(args = {})
args = {:users => [], :result => {}}.merge(args) #defaults
result = args[:result]
if not args[:users].include? self.login #prevent duplicates
result.merge!({:user => {:id => self.id,
:login => self.login,
:role => self.role,
:employees => employee_tree(args[:users] + [self.login], args[:result])
}
})
end
logger.info result.inspect
return result
end
def employee_tree(users, result)
if self.employees.length > 0
self.employees.each {|emp| (emp.management_tree({:users => users, :result => result})) }
end
return result
end
Now... it's supposed to return something like this:
{:user=>{:login=>"me", :id=>1, :role=>2,
:employees=>{
:user => {:login => "2", ...},
:user => {:login => "3",
:employees => {...}
}
}}
Some console output:
% bundle exec script/console
Loading development environment (Rails 2.3.8)
>> require "awesome_print"
=> []
>> ap User.find(1).management_tree[:employees]
nil
=> nil
>> ap User.find(1).management_tree
{
:user => {
:employees => {...},
:role => 2,
:login => "me",
:id => 1
}
}
=> {:user=>{:employees=>{...}, :role=>2, :login=>"me", :id=>1}}
>>
now... it says that employees is nil... but it shouldn't be... it should have 3 hashes ... =\
but also, what does {...} mean? it seams terribly ambiguous
Ruby is clever about recursive structures and will use "..." instead of looping indefinitely.
For example:
a = [1, 2]
a << a # a is now recursive, since it contains itself
a.to_s # => [1, 2, [...]]
a[2][2][2][2][2][2][2] == a # => true
In your case, the {...} refers to any of the hashes already in the process of being outputed.
Maybe what you meant to do was to insert a copy of a hash? In the simple array example:
a = [1, 2]
a << a.dup # a is not recursive
a.to_s # => [1, 2, [1, 2]]

ActiveRecord find_or_build_by

I would like to perform:
XXX.find_or_build_by_language_id(attributes)
I found
XXX.find_or_initialize_by_language_id(attributes)
but that only set language_id and no other attributes. Even if I manually sets the attributes, the record is not saved when I perform XXX.save.
I just read Rails - find or create - is there a find or build?, which seems related to my problem but does not fit my needs.
Edit
Let's use this scenario
# db/migrations/create_models.rb
class CreateModels < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :name
end
create_table :employees do |t|
t.string :name
t.string :city
t.references :company
end
end
end
-
# app/models/employee.rb
class Employee < ActiveRecord::Base
belongs_to :company
end
-
# app/models/company.rb
class Company < ActiveRecord::Base
has_many :employees
end
-
# rails console
:001> c = Company.new
=> #<Company id: nil, name: nil>
:002> c.employees
=> []
:003> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'bar')
=> #<Employee id: nil, name: "foo", city: "bar", company_id: nil>
:004> c.employees
=> []
:005> c.save
=> true
:006> c.employees
=> []
:007> e.save
=> true
:008> c = Company.first
=> #<Company id: 1, name: nil>
:009> c.employees
=> [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>]
:010> e = c.employees.find_or_initialize_by_name(:name => 'foo', :city => 'baz')
=> #<Employee id: 1, name: "foo", city: "bar", company_id: 1>
:011> e.city = 'baz'
=> "baz"
:012> c.employees
=> [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>]
:013 > c.save
=> true
:014> c.employees
=> [#<Employee id: 1, name: "foo", city: "bar", company_id: 1>]
Problems
:004 => The Employee from :003 is not added to c.employees
:006 => The Employee from :003 is saved with c
:010 => The city attribute of employee is not set
:014 => THe city attribute of employee is not updated when saving company
How about this?
employee_attrs = {:name => 'foo', :city => 'bar'}
e = c.employees.where(employee_attrs).first || c.employees.build(employee_attrs)
For the record, here is the implementation I came with. It can probably be simpler, but it suits my needs:
module ActiveRecord
module Associations
class AssociationCollection < AssociationProxy
alias_method :old_method_missing, :method_missing
def method_missing(method_id, *arguments, &block)
if /^find_or_build_by_([_a-zA-Z]\w*)$/ =~ method_id.to_s
names = $1.split('_and_')
find_or_build_by(names, *arguments)
else
old_method_missing(method_id, *arguments, &block)
end
end
def find_or_build_by(names, *arguments)
values = arguments[0]
throw InvalidArgument unless values.keys.first.kind_of?(String)
record = Array.new(self).find do |r|
names.inject(true) do |memo, name|
memo && (values[name].to_s == r.send(name).to_s)
end
end
if record
sanitized_values = record.send(:sanitize_for_mass_assignment, values)
sanitized_values.each {|k, v| record.send("#{k}=", v)}
else
record = build(values)
end
return record
end
end
end
end
I tried the following code for my Rails 4.2.x app.
#config/initializers/collection_proxy.rb
ActiveRecord::Associations::CollectionProxy.class_eval do
alias_method :old_method_missing, :method_missing
def method_missing(method_id, *arguments, &block)
if /^find_or_build_by([_a-zA-Z]\w*)$/ =~ method_id.to_s
names = $1.split('_and_')
find_or_build_by(names, *arguments)
else
old_method_missing(method_id, *arguments, &block)
end
end
def find_or_build_by(names, *arguments)
where(names).first || build(names)
end
end
You can use it like this.
XXX.find_or_build_by(attributes)

Resources