Ruby on Rails JSON to Model Conversion - ruby-on-rails

def create
#match_data = JSON.parse(request.raw_post)
#match = #match_data["match"]
match = Match.new
match.match_length = #match["match_length"]
match.quest = #match["quest"]
match.humans_team_stats = #match["humans_team_stats"]
match.supernaturals_team_stats = #match["supernaturals_team_stats"]
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
match.save!
user_match_stats = UserMatchStat.new
user_match_stats.user = user_account
user_match_stats.match = match
user_match_stats.kills = user["kills"]
user_match_stats.deaths = user["deaths"]
user_match_stats.assists = user["assists"]
user_match_stats.damage_dealt = user["damage_dealt"]
user_match_stats.damage_taken = user["damage_taken"]
user_match_stats.first_blood = user["first_blood"]
user_match_stats.save!
match.user_match_stats << user_match_stats
end
render json: #match_data
end
is this literally the best way to map an incoming json object to a rails model and make a record for it? There's gotta be a better way than this...

Provided your model's columns are the same as the keys in the JSON object (which it looks like they are from your code snippet), you can use symbolize_keys to turn the json objects into a ruby hash that your rails model will accept as commit params. It could look something like the following:
match = Match.create(match_length: #match["match_length"], quest: #match["quest"], humans_team_stats: #match["humans_team_stats"], supernaturals_team_stats: #match["supernaturals_team_stats"])
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
UserMatchStat.create(user.symbolize_keys)
end

Related

how append an object to association relation in rails?

In a rails 4.1 application I need to add an object to an "AssociationRelation"
def index
employee = Employee.where(id_person: params[:id_person]).take
receipts_t = employee.receipts.where(:consent => true) #gives 3 results
receipts_n = employee.receipts.where(:consent => nil).limit(1) #gives 1 result
#I would need to add the null consent query result to the true consent results
#something similar to this and the result is still an association relation
#receipts = receipts_t + receipts_n
end
Is there a simple way to do this?
A way of solving this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
receipts_t = employee_receipts.where(consent: true)
receipts_n = employee_receipts.where(consent: nil).limit(1)
#receipts = Receipt.where(id: receipts_t.ids + receipts_n.ids)
end
Unfortunately .or() can't be used here because it's only available from Rails v5.0.0.1
you could do this way
receipts_t_ids = employee.receipts.where(:consent => true).pluck(:id)
receipts_n_ids = employee.receipts.where(:consent => nil).limit(1).pluck(:id)
#receipts = Receipt.where(id: receipts_t_ids + receipts_n_ids)
To avoid extra queries and keeping arrays in memory, you can use or
Like this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
#receipts =
employee_receipts.where(consent: true).or(
employee_receipts.where(consent: nil).limit(1)
)
end

Chaining multiple ActiveRecord `or` queries

I've got an array of columns that I want to loop through and optionally chain an or query onto an ActiveRecord query chain. I can get it to work, but the resulting query appends the or onto the query chain, therefore making the columns in my inital query optional. Here's my class:
class Claim
class MatchingAttributeFinder
ATTRIBUTE_GROUPS_TO_MATCH = [
["teacher_reference_number"],
["email_address"],
["national_insurance_number"],
["bank_account_number", "bank_sort_code", "building_society_roll_number"],
].freeze
def initialize(source_claim, claims_to_compare = Claim.submitted)
#source_claim = source_claim
#claims_to_compare = claims_to_compare
end
def matching_claims
claims = #claims_to_compare.where.not(id: #source_claim.id)
ATTRIBUTE_GROUPS_TO_MATCH.each do |attributes|
vals = values_for_attributes(attributes)
next if vals.blank?
concatenated_columns = "CONCAT(#{attributes.join(",")})"
claims = claims.or(
Claim.where("LOWER(#{concatenated_columns}) = LOWER(?)", vals.join)
)
end
claims
end
private
def values_for_attributes(attributes)
attributes.map { |attribute|
#source_claim.read_attribute(attribute)
}.reject(&:blank?)
end
end
end
The generated SQL looks like this:
SELECT "claims".* FROM "claims" WHERE (((("claims"."submitted_at" IS NOT NULL AND "claims"."id" != 'a7b25b99-4477-42b1-96ab-8262582c5541' OR (LOWER(CONCAT(teacher_reference_number)) = LOWER('0902344'))) OR (LOWER(CONCAT(email_address)) = LOWER('genghis.khan#mongol-empire.com'))) OR (LOWER(CONCAT(national_insurance_number)) = LOWER('QQ891011C'))) OR (LOWER(CONCAT(bank_account_number,bank_sort_code,building_society_roll_number)) = LOWER('34682151972654123456789/ABCD')))
But what I actually want is more like this:
SELECT "claims".* FROM "claims" WHERE "claims"."submitted_at" IS NOT NULL AND "claims"."id" != 'd6a53b4d-c569-49e6-a2ea-ac44b69b0451' AND (LOWER(concat(teacher_reference_number)) = LOWER('0902344') OR LOWER(concat(email_address)) = LOWER('genghis.khan#mongol-empire.com') OR LOWER(concat(national_insurance_number)) = LOWER('QQ891011C') OR LOWER(concat(bank_account_number,bank_sort_code,building_society_roll_number)) = LOWER('34682151972654123456789/ABCD'))
Is there any way to set up something like an empty scope that I can chain my OR queries to?
Try chaning all the "or" together first and then chain the original query
def matching_claims
claims = #claims_to_compare.where.not(id: #source_claim.id)
ors = nil
ATTRIBUTE_GROUPS_TO_MATCH.each do |attributes|
vals = values_for_attributes(attributes)
next if vals.blank?
concatenated_columns = "CONCAT(#{attributes.join(",")})"
aux = Claim.where("LOWER(#{concatenated_columns}) = LOWER(?)", vals.join)
if ors.nil?
ors = aux
else
ors = ors.or(aux)
end
end
claims.merge(ors)
end

rails - get the SQL of a model save

I need to be able to output the SQL UPDATES that would be generated by Rails, without actually running them or Saving the records. I will be outputting the SQL updates to a file instead.
Is there a way to do this in Rails, without using string interpolation?
Is it possible to do something like the following:
p = Post.where (something)
p.some_value = some_new_value
p.to_sql??? # how to generate the update statement
rather than:
"UPDATE TABLE SET field_1 = #{new_field} WHERE ID = " etc etc
Took from #R.F. Nelson and wrap it to a method. You could just calling to_update_sql with your model as the argument to get the SQL.
def to_update_sql(model)
return '' if model.changes.empty?
table = Arel::Table.new(model.class.table_name)
update_manager = Arel::UpdateManager.new(model.class)
update_manager.set(model.changes.map{|name, (_, val)| [table[name], val] })
.where(table[:id].eq(model.id))
.table(table)
return update_manager.to_sql
end
post = Post.first
post.some_value = xxxx
to_update_sql(post)
# => UPDATE `posts` SET `some_value` = xxx WHERE `posts`.`id` = 1
Taken from this post:
You can achieve this goal with AREL:
# Arel::InsertManager
table = Arel::Table.new(:users)
insert_manager = Arel::InsertManager.new
insert_manager.into(table)
insert_manager.insert([ [table[:first_name], 'Eddie'] ])
sql_transaction = insert_manager.to_sql
File.open('file_name.txt', 'w') do |file|
file.write(sql)
end
# Arel::UpdateManager
table = Arel::Table.new(:users)
update_manager = Arel::UpdateManager.new
update_manager.set([[table[:first_name], "Vedder"]]).where(table[:id].eq(1)).table(table)
sql_transaction = update_manager.to_sql
File.open('file_name.txt', 'w') do |file|
file.write(sql)
end
Here you can find all Arel managers, like delete_manager.rb, select_manager.rb and the others.
Good read: http://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby.html

How do I test with Rspec an initialize method that invokes find_or_initialize_by?

How do I write a test to check for the find_or_initialize_by block in this initialize method?
def initialize(document, user)
#document = document
#api = #document.api_version
#template = #document.template
#user = user
#brand = #user.brand
#vars = master_var_hash.extend Hashie::Extensions::DeepFind
template_variables.each do |t|
#document.template_variables.find_or_initialize_by(name: t.name) do |d|
d.name = t.name
d.tag = t.tag
d.box_name = t.box_name
d.designator = t.designator
d.order_index = t.order_index
d.master_id = t.id
d.editable = t.editable
d.editable_title = t.editable_title
d.html_box = t.box.stack.html_box if #api == :v3
d.text = t.name == 'title' ? default_title : user_value(t)
end
end
end
I want to be able to test that the right values have been assigned to the #document's TemplateVariables from the class' TemplateVariables. In my coverage report I can't even hit inside the find_or_initialize_by block.
My test for size doesn't really check what I want to test here:
describe 'template_variables' do
it 'initializes all the new vars per document' do
expect(document.template_variables.size).to eq subject.master_var_hash.size
end
end
How can I write a test to check all those values and cover those lines?
You could use
expect(document).to receive_message_chain(:template_variables, :find_or_initialize_by).exactly(8).times
but it's a mess and you would have to also check if each call got proper parameters.
I would suggest extracting this to a method:
Document#initialize_variables(template_variables)
then you could test it as simply as
expect(document).to receive(:initialize_variables).with(expected_hash)
and then you can cover Document#initialize_variables with specs and test it's behavior in depth.

Is using local variable instead of instance variable improve the performance(save memory) in Rails?

I have created Rails application and I have used lots of instance variables and most of them are not required in the views. Do I need to replace the unused instance variables for improving the performance?
Sample code:
def show
custom_fields_data = fetch_custom_field_data
#selected_custom_fields_opt_from_view = []
if custom_fields_data.present?
#listings = #listing.category.listings.where("price_cents!=? AND open= ?",0,true).reject { |l| l.author.main_admin? }
#selected_custom_fields_opt_from_view = custom_fields_data.map do |custom_field_data|
CustomField.find(custom_field_data[0]).options.find(custom_field_data[1])
end
#listings.each do |listing|
# array to store the selected a custom field's option from Database
selected_custom_fields_opt_from_db = []
listing.custom_field_values.each do |custom_field_value|
selected_custom_fields_opt_from_db.push(custom_field_value.selected_options.first)
end
if selected_custom_fields_opt_from_db.uniq.sort == #selected_custom_fields_opt_from_view.uniq.sort || (#selected_custom_fields_opt_from_view - selected_custom_fields_opt_from_db).empty?
similar_listing.push(listing)
end
end
#listings = similar_listing
end
#listing_with_filters = similar_listing.present? ? #listings.first : #listing
#selected_tribe_navi_tab = "home"
unless current_user?(#listing.author)
#listing.increment!(:times_viewed)
end
#current_image = if params[:image]
#listing.image_by_id(params[:image])
else
#listing.listing_images.first
end
#prev_image_id, #next_image_id = if #current_image
#listing.prev_and_next_image_ids_by_id(#current_image.id)
else
[nil, nil]
end
payment_gateway = MarketplaceService::Community::Query.payment_type(#current_community.id)
process = get_transaction_process(community_id: #current_community.id, transaction_process_id: #listing.transaction_process_id)
form_path = new_transaction_path(listing_id: #listing.id)
delivery_opts = delivery_config(#listing.require_shipping_address, #listing.pickup_enabled, #listing.shipping_price, #listing.shipping_price_additional, #listing.currency)
#category = #listing.category
#template_listing = #category.template_listing
if #current_user
# For Pivot table
#selected_custom_field = params[:custom_field] if params[:custom_field]
#listing_for_pivot = Listing.new
#listing_images = #listing.listing_images
#shape = get_shape(#listing.listing_shape_id)
#unit_options = ListingViewUtils.unit_options(#shape[:units], unit_from_listing(#template_listing)).first if #shape
#custom_field_questions = #category.custom_fields
#numeric_field_ids = numeric_field_ids(#custom_field_questions)
#category_tree = CategoryViewUtils.category_tree(
categories: ListingService::API::Api.categories.get(community_id: #current_community.id)[:data],
shapes: get_shapes,
locale: I18n.locale,
all_locales: #current_community.locales
)
if #template_listing.present?
#listing_for_pivot.title = #template_listing.title
#listing_for_pivot.description = #template_listing.description
#listing_images = #template_listing.listing_images if #template_listing.listing_images.present?
#listing_for_pivot.listing_shape_id = #template_listing.listing_shape_id
end
if (#current_user.location != nil)
temp = #current_user.location
temp.location_type = "origin_loc"
#listing_for_pivot.build_origin_loc(temp.attributes)
else
#listing_for_pivot.build_origin_loc(:location_type => "origin_loc")
end
#custom_field_area = CategoryCustomField.where(category_id: #category.id, custom_field_id: #category.custom_fields.pluck(:id))
#row = #category.custom_field_row
#row = #custom_field_area.first.custom_field if #row.nil? && #custom_field_area.first
#column = #category.custom_field_column
#column = #custom_field_area.second.custom_field if #column.nil? && #custom_field_area.second
#filters = #category.custom_field_filters
#filters = #custom_field_area.all.from(1).map { |category_custom_field| category_custom_field.custom_field } if #filters.nil? && #custom_field_area.size > 2
#selected_value_for_filter = []
if #filters.present?
if #selected_custom_field
#filters.each do |filter|
if (#selected_custom_field["#{filter.id.to_s}_"])
#selected_value_for_filter.push(filter.options.find(#selected_custom_field["#{filter.id.to_s}_"]))
else
#selected_value_for_filter.push(filter.options.first)
end
end
else
#filters.each do |filter|
#selected_value_for_filter.push(filter.options.first)
end
end
end
# Pivot table section end
end
#applicant = #category.listings.pluck(:author_id).uniq
#suggested_business_accounts = #category.people.where("people.id NOT IN (?)", #applicant);
if #suggested_business_accounts.present?
#business_locations =
#suggested_business_accounts.map do |person|
person.location
end
#business_locations.compact!
end
render locals: {
form_path: form_path,
payment_gateway: payment_gateway,
# TODO I guess we should not need to know the process in order to show the listing
process: process,
delivery_opts: delivery_opts,
listing_unit_type: #listing.unit_type
}
end
It is not recommended to use instance variables if you don't want to send them to views. The scope of the variables should be narrowest, therefore in your case if you are not using instance variables in the views you should convert them to local.
Using instance variables instead of local variables is a bad idea at least memory-wise.
Instance variable exists while the object that holds it exists. On the contrary, local variable exists only inside method/block it is defined.
Garbage collector does not care whether you use instance variable elsewhere beyond the method or not.
Thus, if you have instance variables, which you only intend to use within the method - change them to local ones.

Resources