Return values were not extracted in Ruby - ruby-on-rails

def get_dept_class_type
departments, classifications, types = [["-- select one --"]] * 3
Department.all.each do |d|
departments << d.name
end
Classification.all.each do |c|
classifications << c.name
end
Type.all.each do |t|
types << t.name
end
return departments, classifications, types
end
def new
#departments, #classifications, #types = get_dept_class_type
end
Hello guys,
above is my code in Ruby to assign the return values from "get_dept_class_type" function to "def new" instance variables. The problem is, the return values from the "get_dept_class_type" function were not extracted, So all instance variables have the same values. Each instance variable is the containing value of a select tag in html form.
The values of department, classification, type select tags have these the same content:
Information Technology
Administrative Department
Internal Use
Top Secret
Confidential
Public
Policy
Procedure Manual
Please it help me to figure this out. Thank you in advanced.

Your main problem is -
departments, classifications, types = [["-- select one --"]] * 3
changed it to -
departments, classifications, types = Array.new(3) { ["-- select one --"] }
Lets debug it :-
([[]] * 3).map(&:object_id) # => [72506330, 72506330, 72506330]
but
Array.new(3) { [] }.map(&:object_id) # => [76642680, 76642670, 76642520]
You can see, that all the inner objects are basically same object. Basically you have created an array of array, say a, where all the element arrays are same object. Thus if you have modified, say a[0], you can see the same change when you would inspect a[1] or a[2]. But if you do create the same array of array, a as , Array.new(3) { [] }, then each inner element array of a, will be different object. Thus if you say modify a[0], then a[1] and a[2] will be intact.
Worth to read Common gotchas.

Related

categories ordering not as expected

I have a categories model with many categories, that has an attribute called tag.
A main category has a tag e.g 1
and a child category has a tag 1-1
so in order to show those categories one after the other in correct order, i order them by tag using the following
#categories = Category.order("tag ASC")
but for some reason let say i have tags,
[1, 1-1, 1-2, 3,4, 10, 10-1 ]
which is the desired ordering i want
the ordering becomes
[1, 1-1, 1-2, 10,10-1, 3,4 ]
how can i fix that?
This is because tag is a string and if you order strings ASC you order them alphabetically. You'll have to write a custom method to sort records by.
Let's say your sort method is called order_by_tag. Then you can order your categories like so: Category.all.sort_by(&:order_by_tag).
Note that this is more difficult if you don't want to iterate over a large number of records every time.
To write a custom sorting method, you'll have to come up with a way to calculate a value of your tag. You could do something like this:
def order_by_tag
tag.split('-').reverse.each_with_index.map do |tag, index|
tag.to_i * 2**(index + 1) }
end.reduce(:+) # => 1*(2^1) + 10*(2^2) = 42
end
Or, shorter:
def order_by_tag
tag.split('-').reverse.each_with_index.map { |tag, index| tag.to_i * 2**(index + 1) }.reduce(:+) # => 1*(2^1) + 10*(2^2) = 42
end
But this example assumes a limit on a possible tag value.
EDIT:
There is an easier way. You can sort records by multiple attributes, if you return an array:
def order_by_tag
tag.split('-').map { |t| t.to_i } # "10-1" => [10, 1]
end
This way Rails will sort records by the first member of the array, then the second and so forth. This way is much better than my first example because it's much easier and values of your categories won't overlap.
SUMMARY:
#categories = Category.all.sort_by(&:order_by_tag)
def order_by_tag
tag.split('-').map { |t| t.to_i }
end

Create new table in Rails joining fields from another database

In my church website, many members are married couples. I have a csv file from the membership database, which uses a seven-digit identifier that looks like this:
00903 1
and the spouse of that person will be
00903 2
What I would like to do is to create a table of couples and their anniversaries. How would I get a row to contain
her_first_name:string his_first_name:string family_name:string.
In other words, how would I combine information from two records into one record, when the only way to identify how the two are linked is the last "digit" in a string? Is Regex involved, and if so, what does the query look like? Thanks
My approach would be to do something like this (I'm assuming that it's always the same format, where for a couple, the first 5 digits are actually the same. I'm also assuming that you need to check if each record has a spouse, not that you already know they are married. Finally, I'm assuming you have a model named Couple with an associated couples table, a model named Person for individual members which has ind_id field as the identifier, and I'm making up the row positions for each field.):
individuals = [] # an array that can hold each record until a spouse is found in the csv
csv.each do |row|
id_regexp = Regexp.new(row[0][0..4] + '.') # creates a regexp out of the first 5 characters in the id, then adds '.' to match any following characters
spouse = individuals.select { |individual| individual.ind_id =~ id_regexp }.first #searches individuals for that regexp, returns the first instance if it exists, or nil otherwise
if spouse
couple = Couple.new(wife_name: spouse.first_name, husband_name: row[1], last_name: row[2], anniversary: row[3], etc.) # if a spouse was found create a Couple record
couple.save!
individuals.delete(spouse) # remove this record from the array so there is one less record to iterate over on the next loop
else
individuals << Person.new(first_name: row[1], last_name: row[2], etc.) # build a Person and store it in the array. You could persist the Person to the db if you are saving individuals with this import as well
end
end
Now, you could add a check in there, given a spouse is found, that checks to see which is the husband and which is the wife depending on the 1 or the 2 in the ind_id[6] slot:
if spouse.ind_id[6] == '1'
couple.wife_name = spouse.first_name
couple.husband_name = row[1]
else
couple.husband_name = spouse.first_name
couple.wife_name = row[1]
end
This would obviously go in between the couple = Couple.new(... and the couple.save!.
Anyway, I had to assume a lot because of the amount of detail provided in your OP, but hopefully this gives you a template to go off of. If you don't have Person class, you could just create a hash and push that into individuals each time and compare individual[:ind_id] in the select.
Note
I used Regexp because you specified this. Could just as easily do
base_id = row[0][0..4]
spouse = individuals.select { |individual| individual.ind_id[0..4] == base_id }.first

RubyOnRails multiple scopes composition

I got a Product model with has_many Types table and several scopes:
class Product < ActiveRecord::Base
has_many :product_types
has_many :types, through: :product_types
scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }
end
When I try to use one scope (Product.type1 for example) all goes well, but two scopes at a time (Product.type1.type2) returns an empty query. Yes, one product may have multiple types.
Final goal is to create a filter of products by type with checkboxes form. When I check type1 and type2 I want to see all my products that have Type1 and Type1 at the same time.
UPD 1
So I've tried to do several queries and then & them as #aaron.v suggested. I wanted to do the logic inside of the function so:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
My point was to iterate through each type, collect all products and then & them inside the function.
The problem is when I'm iterating, each loop returns string instead of array of hashes.
Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.
types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.
What am I doing wrong?
SOLUTION 1
Suggested by #aaron.v, explained below
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
SOLUTION 2
Found on reddit.
In this function you are fetching all products with at least one required type and than grouping only ones that have required count of types. Thus, you get only those products that belongs to every type at the same time.
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count)
end
If you have a database background, this would be pretty obvious as to why you wouldn't be able to find products with multiple types based off how you are writing your scopes.
Database queries that are written from these scopes will multiply the rows to ensure that a product that has many types, will have a distinct row for each type. Your query when you combine both scopes is writing
where `types`.name = "Type1" AND `types`.name = "Type2"
This will never happen since columns aren't added with multiples of the same row on a join.
What you want to do is have a method that you can pass an array of type names to find it
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct
end
This method can accept one name by itself or an array of names for the types you want to find
If you pass an array of type names like ["type1", "type2"], this will result in a query like
where `types`.name in ("type1", "type2")
And then you should see the results you expect
UPDATE
To revise what you need in finding products that have all types given to your method, I decided to do this
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
This method requires an array of type names(even if it is one type, pass an array with one type in it). What it will do is find all products with one specific type name, only select the product id(which will be lighter on your DB) and map it into an array which will get dumped into the product_ids array. after it has looped over each type name, it will find all products that intersect in each of the arrays resulting with products that have all types passed in.

Produce a report by getting data from multiple tables

I have 4 tables:
key: id, name
project: id, name
project_key: id, key_id, project_id
project_report: id, status, project_id, key_id
project_c_report: id, status, project_id, key_id, c_id
I want to produce a report using those tables:
The output should be:
Key.name, project_report.status, project_c_report.status
I was able to do this by getting all the keys from a project, and loop over them
array = []
project.keys.each do |k|
p = ProjectReport.where(keyword_id: k, project_id: p.id).map(&:status)
c = ProjectCReport.where(keyword_id: k, project_id: p.id, c_id:1).map(&:status)
array << {name: k.name, pr: p, pcr: c}
end
array
The problem is that I am doing a lot of selects and everything is slow, can someone help me please with a better way of doing this.
Thank you
First, create a function in your DataBase. This is just a brief example, and also its done in PostgreSQL but shouldnt difer much
from MySQL, SQLServer, etc
Function get_myreport(key_id integer, project_id integer [As many params as you'd like the function to get))
pProject ALIAS FOR $1;
pKey ALIAS FOR $2;
BEGIN
CREATE TEMP TABLE IF NOT EXISTS tmp_project_report(id integer, project_name character varying, *All the values you want to see in the report);
TRUNCATE tmp_project_report;
INSERT INTO tmp_project_report(all the columns)
SELECT a.table1_fields, b.table2_fields, c.table3_fields, d.table4_fields, e.table5_fields
FROM table1 a, table2 b, table3 c, table4 d, table5 e
WHERE
a.key = pKey
AND b.project_key = pProject
END;
Then, in your controller's method you call the up the function like this
myFunction = ActiveRecord:Base.connection.execute = "Select get_myreport("param1, param2, etc...")
You will have to make a model where you put all the fields that are on the temp_table you've made, and also you will set the temp_table as the self.table_name
Then, in your view, you'd only have to iterate on your collection and display the values accordingly
#report = TempTable.all
<% #report.each_do |report| %>
<% report.value1 %>
<% etc... %>
<% end %>
Figure out the database query, then query the database directly from your model:
def records
connection = ActiveRecord::Base.connection
records = connection.select %Q {
SELECT key.name, project_report.status, project_c_report.status
FROM ...
JOIN ...
;
}
records
end
Here is something you can try if you choose to keep this within Rails (note that the following query is untested and is shown for concept only):
report_data = Project.joins(project_key: :key)
.joins('left join project_reports on project_keys.project_id = project_reports.project_id and project_keys.key_id = project_reports.key_id
left join project_c_reports on project_keys.project_id = project_c_reports.project_id and project_keys.key_id = project_c_reports.key_id')
.where('project_c_reports.c_id = ?', 1)
.select('projects.name, project_reports.status as report_status, project_c_reports.status as c_report_status')
This should give you an array of Project objects each including the selected three attributes name, report_status, c_report_status. To get these values in an array of these three elements you could do:
report_data.map { |p| [ p.name, p.report_status, p.c_report_status ] }
The type of join for the query depends on your requirement. Given the index are in place the query should be better compared to how it looks in code!

Best way to order records by predetermined list

In order to keep things simple I have avoided using an enum for an attribute, and am instead storing string values.
I have a list of all possible values in a predetermined order in the array: MyTypeEnum.names
And I have an ActiveRecord::Relation list of records in my_recs = MyModel.order(:my_type)
What is the best way to order records in my_recs by their :my_type attribute value in the order specified by the array of values in MyTypeEnum.names ?
I could try this:
my_recs = MyModel.order(:my_type)
ordered_recs = []
MyTypeEnum.names.each do |my_type_ordered|
ordered_recs << my_recs.where(:my_type => my_type_ordered)
end
But am I killing performance by building an array instead of using the ActiveRecord::Relation? Is there a cleaner way? (Note: I may want to have flexibility in the ordering so I don't want to assume the order is hardcoded as above by the order of MyTypeEnum.names)
You are definitely taking a performance hit by doing a separate query for every item in MyTypeEnum. This requires only one query (grabbing all records at once).
ordered_recs = Hash[MyTypeEnum.names.map { |v| [v, []] }]
MyModel.order(:my_type).all.each do |rec|
ordered_recs[rec.my_type] << rec
end
ordered_recs = ordered_recs.values.flatten
If MyTypeEnum contains :a, :b, and :c, ordered_recs is initialized with a Hash of Arrays keyed by each of the above symbols
irb(main):002:0> Hash[MyTypeEnum.names.map { |v| [v, []] }]
=> {:a=>[], :b=>[], :c=>[]}
The records are appended to the proper Array based on it's key in the Hash, and then when all have bene properly grouped, the arrays are concatenated/flattened together into a single list of records.

Resources