ActiveModel::MissingAttributeError (can't write unknown attribute 'Russian Federation'): - ruby-on-rails

Getting a very strange error when running reduce on an activemodel relation. It seems like calling "c.name" in my reduce code causes the error. c.name returns the string "Russian Federation". Am I using reduce incorrectly?
Here is the error:
[2014-03-17T21:12:40.174655 #9240] FATAL -- :
ActiveModel::MissingAttributeError (can't write unknown attribute `Russian Federation'):
app/controllers/registrations_controller.rb:96:in `block in pluck_countries'
app/controllers/registrations_controller.rb:96:in `pluck_countries'
Larger stacktrace from the console:
#third_party_countries.reduce(#hsh) {|hsh, c| hsh[c.name] = ThirdPartyShipping.first }
ActiveModel::MissingAttributeError: can't write unknown attribute `Russian Federation'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods/write.rb:47:in `write_attribute'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods/dirty.rb:70:in `write_attribute'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/attribute_methods.rb:341:in `[]='
from (irb):3:in `block in irb_binding'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `each'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `reduce'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/activerecord-4.0.2/lib/active_record/relation/delegation.rb:63:in `method_missing'
from (irb):3
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands/console.rb:90:in `start'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands/console.rb:9:in `start'
from /var/www/html/babiators.com/landf/vendor/bundle/ruby/2.0.0/gems/railties-4.0.2/lib/rails/commands.rb:62:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
Here is the code:
#third_party_countries = Country.third_party_countries
#hsh = HashWithIndifferentAccess.new
#third_party_countries.reduce(#hsh) {|hsh, c| hsh[c.name] = c.third_party_shipping }
Country schema:
create_table "countries", force: true do |t|
t.string "name"
t.float "shipping_rate"
t.integer "third_party_shipping_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "product_id"
end
Country model:
class Country < ActiveRecord::Base
belongs_to :third_party_shipping
has_and_belongs_to_many :products
has_many :addresses
validates_presence_of :name
validates_uniqueness_of :name
before_create :init
after_initialize :init
scope :shippable, -> { where(third_party_shipping_id: nil) }
scope :third_party_countries, -> { where.not(third_party_shipping_id: nil) }
def shipping_price
self.shipping_rate * 100
end
def free_shipping
self.shipping_rate <= 0 and self.third_party_shipping_id.nil?
end
def paid_shipping
!self.free_shipping
end
def shipping_display
if self.free_shipping
"free"
elsif self.paid_shipping
self.shipping_rate
end
end
private
def init
if self.shipping_rate.blank?
self.shipping_rate = 0
end
end
end

Since you're declaring #hsh outside the loop, you have no need of the extra complexity introduced by inject/reduce. Just use each:
#hsh = HashWithIndifferentAccess.new
#third_party_countries.each{ |c| #hsh[c.name] = c.third_party_shipping }
The problem with your existing code is that, with inject/reduce, the return value of the block is passed as the first argument to the next invocation. You need to return hsh from your block, otherwise the value of c.third_party_shipping is passed as hsh to the next invocation, and you're effectively doing c.third_party_shipping[c.name].
You could make it work by returning hsh...
#third_party_countries.reduce(#hsh) do |hsh, c|
hsh[c.name] = c.third_party_shipping
hsh
end
But you shouldn't. You don't need this functionality. each_with_object is the correct method to use:
#hsh = #third_party_countries.each_with_object(HashWithIndifferentAccess.new) do |hsh, c|
hsh[c.name] = c.third_party_shipping
end
You could also just map the collection to an array, and initialize your hash with that array:
#hsh = HashWithIndifferentAccess[#third_party_countries.map { |c| [c.name, c.third_party_shipping] }]
This relies on the ability to freely convert between arrays and hashes. The array [[a, b], [c, d]] converts to the hash { a => b, c => d }.

Related

rails Calculation (price * quantity)

Hi Im creating an ec site in my rails.
My migration:
(Item) has :name and :price.
(Basket_Item) has :item_id(fk), :basket_id(fk) and :quantity.
The system User will add some items to their basket.
So Basket_items is JOIN Table between (Item) and (Basket)
see like below.
What I want to do:
Get a price of Item and get a quantity from Basket_Items which is selected by user. Then I want to create #total_price = item_price * item_quantity.
Can anyone help me to create the #total_price please.
This is my a try code but it doesn't work on rails console.
Basket_items
class CreateBasketItems < ActiveRecord::Migration[5.2]
def change
create_table :basket_items do |t|
t.references :basket, index: true, null: false, foreign_key: true
t.references :item, index: true, null: false, foreign_key: true
t.integer :quantity, null: false, default: 1
t.timestamps
end
end
end
///
Items
class CreateItems < ActiveRecord::Migration[5.2]
def change
create_table :items do |t|
t.references :admin, index: true, null: false, foreign_key: true
t.string :name, null: false, index: true
t.integer :price, null: false
t.text :message
t.string :category, index: true
t.string :img
t.string :Video_url
t.text :discription
t.timestamps
end
end
end
///
This is my try a code but it doesn't work on rails console.
basket = current_user.prepare_basket
item_ids = basket.basket_items.select(:item_id)
items = basket.items.where(id: item_ids)
items_price = items.select(:price)
items_quantity = basket.basket_items.where(item_id: item_ids).pluck(:quantity)
def self.total(items_price, items_quantity)
sum(items_price * items_quantity)
end
#total_price = basket.total(items_price, item_quantity)
There are a few issues with your code:
You are trying to call a class method on an instance of the class. That's not gonna work, second you are passing in arrays into the calculation.
basket = current_user.prepare_basket
item_ids = basket.basket_items.select(:item_id)
items = basket.items.where(id: item_ids)
items_price = items.select(:price) # => Array of prices from the items in the basket
items_quantity = basket.basket_items.where(item_id: item_ids).pluck(:quantity) # => Array of quantities from the items in the basket
def self.total(items_price, items_quantity)
sum(items_price * items_quantity) # => So this line will do: sum(['12,95', '9.99'] * [1, 3])
end
#total_price = basket.total(items_price, item_quantity)
As you can see, that ain't gonna work. First you need to change the method and remove the self.
def total(items_price, items_quantity)
# ...
end
Now you can call the total method on a basket object: basket.total(items_price, items_quantity)
And inside the total method you need to loop through each items to do the calculation and add all the results.
def total(items_price, items_quantity)
total_price = 0
items_price.each_with_index do |price, index|
total_price += price * items_quantity[index]
end
total_price
end
But this solution could also fail, because you don't know sure that the order in the items_price is matching with the order of items_quantity. So a better approach would be to do the calculation for each basket_item seperate.
# Basket model
def total
total_price = 0
basket_items.each do |basket_item|
total_price += basket_item.total_price
end
total_price
end
# BasketItem model
def total_price
quantity * item.price
end
Now you can call it like this: basket.total

fetch user_profiles by avoiding N+1 query in graphql

I am using graphiql-ruby gem for using graphql. I am trying to get the list of users with its userprofile and its company. However, it consumes more than 5 minutes and hits the api several times. It even crashes my server. Can anyone help me at this, please?
this is what i have done
module Queries
module Users
extend ActiveSupport::Concern
include GraphQL::Schema::Member::GraphQLTypeNames
included do
field :users, [Types::UserType], null: false
field :user, Types::UserType, null: false do
argument :id, ID, required: true
end
field :currentUser, Types::UserType, null: false
end
# Get all users list except admins
def users
User.where.not(roles: ['admin'])
# context[:current_user].logs.info!('User Data Fetched') && user
end
def user(id:)
User.find(id)
end
def current_user
context[:current_user]
end
end
end
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :email, String, null: false
field :user_profile, Types::UserProfileType, null: false
field :grants, [Types::GrantType], null: true
field :companies, [Types::CompanyType], null: true
field :my_companies_with_grants, [Types::CompanyGrantType], null: true
def grants
Grant.where(user_id: object.id)
end
def my_companies_with_grants
current_user = User.find_by(id: object.id)
company = current_user.companies
userid = current_user.id
company.map {|comp| { company: comp, grants: comp.get_user_grants(userid), company_id: comp.id } }
end
end
end
Here is how i am querying for the list of users
query {
employees: users {
id
email
userProfile {
...UserProfile
}
roles
createdAt
companies {
...Company
}
grants {
...Grant
}
}
}
${COMPANY}
${GRANT}
${USER_PROFILE}
why it is taking too long time to load all the users ? How can it be make efficient?
UPDATE
class ApiSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
use GraphQL::Batch
end
Record loader
class RecordLoader < GraphQL::Batch::Loader
def initialize(model)
#model = model
end
def perform(ids)
#model.where(id: ids).each { |record| fulfill(record.id, record) }
ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
end
end
I changed my user_type.rb and did the following for user_profile
field :user_profile, [Types::UserProfileType, null: true], null: false do
argument :id, [ID], required: true
end
def user_profile(ids:)
RecordLoader.for(UserProfile).load_many(ids)
end
But i get "message":"Field 'userProfile' is missing required arguments: ids" issue
I setup the graphql-batch but could not figure out where should i use RecordLoader as per the way I am using(types and resolvers)

How to set attributes with different names than a DB schema

I am a newbie Ruby developer. I cannot figure out how to create an ActiveRecord model with different attributes names than defined in a DB schema
Consider the following schema
create_table "sync_tasks", force: :cascade do |t|
t.string "name"
t.string "path"
t.string "task_type"
t.string "status"
t.boolean "async", default: false
t.boolean "direct_download", default: true
t.datetime "created_at", null: false
t.datetime "completed_at"
t.datetime "updated_at", null: false
end
And I have the following payload
{
"name" : "Sync /var/www/",
"path" : "/var/www",
"directDownload": true,
"async" : false,
"taskType" : "directory"
}
And trying to create my model like that
class SyncTask < ApplicationRecord
TYPE_DB='db'
TYPE_FILE='file'
TYPE_DIRECTORY='directory'
def initialize(params)
# super
#task_type = params[:taskType]
#direct_download = params[:directDownload]
#path = params[:path]
#status = params[:status]
#async = params[:async]
end
end
When I try to save it throws an error
<NoMethodError: undefined method `[]' for nil:NilClass>
Also I am not able to access field like that
new_task = SyncTask.new(allowed_task_params)
new_task.task_type
It throws the following error
#<NoMethodError: undefined method `task_type' for #<SyncTask not initialized>>
In case I uncomment the super call it gives another error
#<ActiveModel::UnknownAttributeError: unknown attribute 'taskType' for SyncTask.>
What I am doing wrong ? How can I use different attributes names and initialize the model by myself ?
Thanks
You can transform the keys , for example:
=> payload = { "name": "Sync /var/www/", "path": "/var/www", "directDownload": true, "taskType": "directory" }
=> h = payload.transform_keys { |key| key.to_s.underscore } # only since v4.0.2
=> h = Hash[payload.map { |(k, v)| [k.to_s.underscore, v] }] # before v.4.0.2
#> {"name"=>"Sync /var/www/", "path"=>"/var/www", "direct_download"=>true, "task_type"=>"directory"}
=> new_task = SyncTask.new(h)
You shouldn't use the initialize method on AR models. If you still need to use initialize, use after_initialize hook. Because with the initialize we have to declare the super, so it is best to use the callback.

How to avoid ActiveRecord::InvalidForeignKey?

I can't figure out how to fix RSpec errors.
My apps is like this.
There's 3 models, CompanyBag, CompanyBagTarget, Company.
CompanyBag is a group of Companies. CompanyBagTarget is association table between CompanyBag and Company.
Probably, CompanyBagTarget doesn't match appropriate Company.
Now, RSpec error is here.
1) CompanyBagsController POST #create creates a new ComapnyBag
Failure/Error: t.save!
ActiveRecord::InvalidForeignKey:
Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails (`eiicon`.`company_bag_targets`, CONSTRAINT `fk_bag_targets_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`)): INSERT INTO `company_bag_targets` (`company_bag_id`, `company_id`, `display_order`) VALUES (38, 13, 0)
# ./app/models/company_bag.rb:22:in `block in update_targets!'
# ./app/models/company_bag.rb:16:in `each'
# ./app/models/company_bag.rb:16:in `each_with_index'
# ./app/models/company_bag.rb:16:in `update_targets!'
# ./app/controllers/company_bags_controller.rb:52:in `create'
# ./spec/controllers/company_bag_controller_spec.rb:55:in `block (4 levels) in <top (required)>'
# ./spec/controllers/company_bag_controller_spec.rb:54:in `block (3 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# Mysql2::Error:
# Cannot add or update a child row: a foreign key constraint fails (`eiicon`.`company_bag_targets`, CONSTRAINT `fk_bag_targets_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`))
# ./app/models/company_bag.rb:22:in `block in update_targets!'
2) CompanyBagsController POST #create creates a new ComapnyBagTarget
Failure/Error: t.save!
ActiveRecord::InvalidForeignKey:
Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails (`eiicon`.`company_bag_targets`, CONSTRAINT `fk_bag_targets_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`)): INSERT INTO `company_bag_targets` (`company_bag_id`, `company_id`, `display_order`) VALUES (39, 13, 0)
# ./app/models/company_bag.rb:22:in `block in update_targets!'
# ./app/models/company_bag.rb:16:in `each'
# ./app/models/company_bag.rb:16:in `each_with_index'
# ./app/models/company_bag.rb:16:in `update_targets!'
# ./app/controllers/company_bags_controller.rb:52:in `create'
# ./spec/controllers/company_bag_controller_spec.rb:62:in `block (4 levels) in <top (required)>'
# ./spec/controllers/company_bag_controller_spec.rb:61:in `block (3 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# Mysql2::Error:
# Cannot add or update a child row: a foreign key constraint fails (`eiicon`.`company_bag_targets`, CONSTRAINT `fk_bag_targets_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`))
# ./app/models/company_bag.rb:22:in `block in update_targets!'
Next here's model's definitions.
class CompanyBag < ApplicationRecord
has_many :targets, class_name: "CompanyBagTarget"
def update_targets!(company_ids:)
destroy_targets
company_ids.reverse.each_with_index do |id, idex|
t = CompanyBagTarget.new
t.company_bag_id = self.id
t.company_id = id
t.display_order = idex
t.save!
end
end
class CompanyBagTarget < ApplicationRecord
belongs_to :company_target
Next, here's controller.
def create
#bag = CompanyBag.new(bag_params)
#target_ids = params[:company_bag][:target_ids].split(",").map{|n| n.split("_")[0].to_i}
if bag_params[:banner].present?
filename = upload_image
#bag.banner = filename
end
if #bag.save
#bag.update_targets!(company_ids: #target_ids)
redirect_to action: 'index'
else
flash[:alert] = #bag.errors.messages.values[0][0]
#target_ids = params[:company_bag][:target_ids]
render 'new'
end
end
Finally, here's controller's spec.
require 'rails_helper'
RSpec.describe CompanyBagsController, type: :controller do
login_admin
let(:user) { #admin }
let(:company_bag) { FactoryGirl.create(:some_company_bag) }
let(:company_bag_target) { {target_ids: "1_インテリジェンステスト株式会社,21_>グローバルウォーカーズ株式会社,33_株式会社コーデセブン"} }
let(:companies) { FactoryGirl.create(:companies) }
let(:valid_attributes) {
{
label: 1,
banner: "https://s3-ap-northeast-1.amazonaws.com/eiicon.prd.contents/image/bags/6dbd5b5b-f242-4e4a-bcd3-de3edc9ae08d.png",
title: "test title",
description: "test title",
target_ids: "1_インテリジェンステスト株式会社,21_グローバルウォーカーズ株>式会社,33_株式会社コーデセブン"
}
}
describe 'POST #create' do
it 'creates a new ComapnyBag' do
expect{
post :create, params: {company_bag: valid_attributes}, session: valid_session
}.to change(CompanyBag, :count).by(1)
end
it 'creates a new ComapnyBagTarget' do
expect{
post :create, params: {company_bag: valid_attributes}, session: valid_session
}.to change(CompanyBagTarget, :count).by(1)
end
Anyone, please. Thanks in advance.
[Appendix]
Next, here's factories.
FactoryGirl.define do
factory :some_company_bag, class: CompanyBag do
label "some label"
banner "https://s3-ap-northeast-1.amazonaws.com/eiicon.prd.contents/image/bags/6dbd5b5b-f242-4e4a-bcd3-de3edc9ae08d.png"
title "Best Company Title Ever!!"
description "Some says description"
html_description "Html desc"
html_keyword "Html Kw"
end
factory :companies, class: Company do
name "複数テスト会社"
name_kana "フクスウテストカイシャ"
premium_status "free"
pr_status_id 0
is_valid 1
after(:create) do |c|
c.staff << FactoryGirl.create(:staff, company_id: c.id)
end
end
end
And tables.
create_table "company_bag_targets", primary_key: ["company_bag_id", "company_id"], force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
t.integer "company_bag_id", null: false
t.integer "company_id", null: false
t.integer "display_order", null: false
t.index ["company_id"], name: "fk_bag_targets_company", using: :btree
end
create_table "company_bags", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
t.string "label", limit: 100, null: false
t.string "banner", limit: 300
t.string "title", limit: 300
t.text "description", limit: 65535
t.string "html_description", limit: 300
t.string "html_keyword", limit: 300
end
create_table "company", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
t.string "name", null: false
t.string "name_kana"
t.integer "prefecture_id"
t.string "url", limit: 1023
t.date "establishment"

Why can't I save an object with a serialized attribute?

I have...
/app/models/search.rb:
serialize :result
def multisearch
self.result = PgSearch.multisearch(self.term)
self.status = "closed"
self.save
return result
end
/db/schema.rb:
create_table "searches", :force => true do |t|
t.string "term"
t.string "status"
t.text "result"
end
I get the following error when I try `self.save?
ArgumentError: wrong number of arguments (2 for 1)
from /Users/steven/.rvm/gems/ruby-1.9.2-p320/gems/arel-3.0.2/lib/arel/expressions.rb:3:in `count'
I get a similar error when I test result.serialize:
ArgumentError: wrong number of arguments (0 for 1)
from /Users/steven/.rvm/gems/ruby-1.9.2-p320/gems/activerecord-3.2.11/lib/active_record/attribute_methods/serialization.rb:49:in `serialize'
How can I fix this?
Answer was to convert to an array before serialization: self.result = PgSearch.multisearch(self.term).to_a

Resources