Mongodb , rails modify non-array - debug kind_of? Array >> true - ruby-on-rails

I've been trying to add to an array (or what ruby is saying is an array), but keep getting an error from mongo which says
Cannot apply $addToSet modifier to non-array
when I try to run
User.collection.update({'id'=> current.id},{'$addToSet'=>{ 'following' => current.id}})
User.collection.update({'id'=> user.id},{'$addToSet'=>{ 'following' => user.id}})
or the mongomapper version
User.push_uniq(current.id, :following => user.id)
User.push_uniq(user.id, :followers => current.id)
When I output
<%= debug #user.following.kind_of? Array %>
returns true
However, when running
db.users.find()
directly agains mongo, I get
{ "_id" : ObjectId("4c4a196f15a79004e0000007"), "email" : "test#test.com", "foll
owers" : null, "following" : null, "password_hash" : "98f2188de42bce1554d08fbc81
d5c99a2c234933", "password_salt" : "25d80a83cfe3d126cdbe9fec2b731ab9ea57c3b8", "
username" : "test" }
I would have expected following and followers to be [], not null.
When I output debug #user.followers, rails shows --- []
My model to create the user is
key :username, :type => String
key :email, :type => String
key :password_hash, :type => String
key :password_salt, :type => String
key :followers, :type => Array
key :following, :type => Array
The error leads me to believe that the user.followers is being found, but can't be updated.
When I change
User.push_uniq(current.id, :testing => user.id)
I don't get an error, so I think i have that part right.
Any suggestions?

This works for be in 0.8 when declaring the key using key :following, Array instead of key :following, :type => Array.
I tried both push_uniq and collection.update, and didn't get errors on either one. In your collection.update example, you do need to use _id: value instead of id: value since that command is being passed to mongo directly.

Turns out this is a bit of an inconsistency with defining keys in mongomapper.
Don't use the :type => qualifier with Arrays.
I deleted the entire collection, removed :type, and recreated everything and now it works.

Related

CSV Upload in rails

I am trying to add parents and their children data in the parent and child table. I have existing data in these tables and I am trying to add further data and I don't want the data to be repeated. Below is the code I am using to upload data. The child has parent_id.
parent.rb
has_many :children, dependent: :destroy
def self.import(file)
CSV.foreach(file.path, headers:true) do |row|
parent = Parent.find_or_update_or_create_by(
parent_1_firstname: row['parent_1_firstname'],
parent_1_lastname: row['parent_1_lastname'],
address: row['address'],
address_line_2: row['address_line_2'],
city: row['city'],
province: row['province'],
postal_code: row['postal_code'],
telephone_number: row['telephone_number'],
email: row['email'],
family_situation: row['admin_notes'],
gross_income: row['gross_income'],
created_by_admin: row['created_by_admin'],
status: row['status']
)
parent.children.find_or_create_by(
firstname: row['firstname'],
lastname: row['lastname'],
dateofbirth: row['dateofbirth'],
gender: row['gender']
)
end
end
child.rb
belongs_to :parent
The error I am facing is when I choose the csv file to be uploaded below is the error which I am getting.
undefined method `find_or_update_or_create_by' for #<Class:0x00007f8797be74b0> Did you mean? find_or_create_by
I have added a sample csv below. Please help me figure out the issue.
parent_1_firstname,parent_1_lastname,address,address_line_2,city,province,postal_code,telephone_number,email,admin_notes,gross_income, created_by_admin ,status,firstname,lastname,dateofbirth,gender
Nav,Deo,College Road,,Alliston,BC,N4c 6u9,500 000 0000,nav#prw.com,"HAPPY",13917, TRUE , Approved ,Sami,Kidane,2009-10-10,Male
undefined method `find_or_update_or_create_by' for
Class:0x00007f8797be74b0 Did you mean? find_or_create_by
AFAIK, there is no find_or_update_or_create_by method in Rails. Unless you have defined it as a class method in the Parent model, you can't call that method on a class. I believe you meant to use find_or_create_by. Change
Parent.find_or_update_or_create_by
to
Parent.find_or_create_by
Update:
You cannot call create unless the parent is saved
Ok, so the parent isn't saved which could be due to any validations has failed. Change Parent.find_or_create_by to Parent.find_or_create_by!(as #jvillian stated) which will raise an exception with the validation error message. Fix the error and you are good to go.
To not have to hard-code various nested loops doing find_or_create_by logic, there is a gem called DutyFree that makes imports and exports like this fairly painless. It intelligently analyses the has_many and belongs_to associations on models and based on these relationships identifies how to properly save each imported row across multiple destination tables. Either a create or an update is performed based on if the data already exists or not.
To demonstrate your example from above, I wrote an RSpec test based on the CSV data you provided:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_complex_spec.rb
There is also a simpler example available with just 6 columns:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_simple_spec.rb
One nice thing about this gem is that after configuring the column definitions to do an import, you get export for free because everything works from the same template. For this example here's the template which allows the column names from your CSV to line up perfectly with the database columns:
IMPORT_TEMPLATE = {
uniques: [:firstname, :children_firstname],
required: [],
all: [:firstname, :lastname, :address, :address_line_2, :city, :province, :postal_code,
:telephone_number, :email, :admin_notes, :gross_income, :created_by_admin, :status,
{ children: [:firstname, :lastname, :dateofbirth, :gender] }],
as: {
'parent_1_firstname' => 'Firstname',
'parent_1_lastname' => 'Lastname',
'address' => 'Address',
'address_line_2' => 'Address Line 2',
'city' => 'City',
'province' => 'Province',
'postal_code' => 'Postal Code',
'telephone_number' => 'Telephone Number',
'email' => 'Email',
'admin_notes' => 'Admin Notes',
'gross_income' => 'Gross Income',
'created_by_admin' => 'Created By Admin',
'status' => 'Status',
'firstname' => 'Children Firstname',
'lastname' => 'Children Lastname',
'dateofbirth' => 'Children Dateofbirth',
'gender' => 'Children Gender'
}
}.freeze
With this in your parent.rb, you can call Parent.df_import(your_csv_object) or Parent.df_export, and the gem does the rest.

Rails with MongoID Embedded Doc No Mapping To My Model

This is probably an issue on my part I have been struggling through some Rails and mongo samples to get up to speed (This is all for learning not for work.) This current project I get a JSON string from a api and I put that in mongo. So changing the document structure is not really an option. I then have been trying to map that into a Rails model so I can use the data.
The JSON is a report that contains many transactions that contain many LogLines.
Snippet from a doc
< Report _id: 583 c3baac0baf90a7ee26f6e,
ReportName: "PtSomIntPerfThreeLevelTest-1480341940187",
TestName: "PtSomIntPerfThreeLevelTest",
Transactions: [{
"colId" => "50437d6c-49c1",
"InfoPlate" => "[0SXdokPL-R13VQZwi]",
"rowId" => "1",
"sortDate" => 1480341975952,
"transactionType" => "REQUEST",
"description" => "description text for my document",
"startDate" => "11/28/2016 14:06:15",
"startDateMS" => 1480341975952,
"endDate" => "11/28/2016 14:06:23",
"endDateMS" => 1480341983069,
"finalEndDate" => "11/28/2016 14:06:23",
"finalEndDateMS" => 1480341983069,
"completeDuration" => "7 seconds",
"completeDurationMS" => 7117,
"feedProcessingDuration" => "7 seconds",
"feedProcessingDurationMS" => 7117,
"logLines" => [{
"id" => "1062b1ca-0f04",
"timestamp" => 1480341975952,
"transactionType" => "REQUEST",
"transactionStep" => "RECEIVE",
"collationId" => "50437d6c-49c1-438a-9b8",
"runName" => "runName-1480341940187",
"msg" => "Import default",
"elapsedSeconds" => "0.0",
"elapsedMS" => 0,
"InfoPlate" => "[0SXdokPL-3rmxW3oH]"
},
I have a report model, and a transaction model ( I will do LogLines after doing 1 at a time) My report model does fine I can get a single report doc based on an ID and it returns the report. I can then do a "report.transactions" and get a json blob of the transactions (almost always multiple transactions in a report) BUT its not recognized as a transaction model (will post all code below) So I cannot say transaction.InfoPlate I get a no such method error. I have relationships in my model but I also have a " field :Transactions, type: Array" which in looking at the rails cast on mongoid is not in theres. Without that I get nothing so the relationship "embeds_many :transactions" does not allow me to get a report.transaction. Sorry if that is confusing my Rails lingo is low. Short and sweat I want to get a report then get the transactions and be able to do transactions.ColID and get the col ID .
My goal is to get a model for each part of the document report, transaction, LogLines. I do not seem to understand how to do that.
Report Model (Works Fine)
class Report
include Mongoid::Document
field :ReportName, type: String
field :TestName, type: String
field :Transactions, type: Array
field :ReportDurationMS, type: String
embeds_many :transactions
end
Transaction Model
class Transaction
include Mongoid::Document
field :colId, type: String
field :InfoPlate, type: String
field :rowId, type: String
field :sortDate, type: String
field :transactionDate, type: String
field :description, type: String
field :startDate, type: String
field :startDateMS, type: Integer
field :endDate, type: String
field :endDateMS, type: Integer
field :finalEndDate, type: String
field :completeDuration, type: String
field :completeDurationMS, type: Integer
field :feedProcessingDuration, type: String
field :feedProcessingDurationMS, type: Integer
field :logLines, type: Array
embedded_in :report, :inverse_of => :transactions
end
Report Controller (Index Method) Debug logger is just there while I hack around
def index
#reports = Report.all
Rails.logger.info("********* #{#reports.inspect} ***********")
end
Transaction Controller (This is what I can't get to return a transaction as a model) I get a transaction back from #report.transactions but its just a string of json as opposed to a ruby model. Or at least I can't call anything like #transaction.colId. Just returns no such method. Transactions is an array there are many so I did try transactions.first.InfoPlate but still to me it seems Rails just seems the transaction that comes back as a string of JSON not an object.?
class TransactionsController < ApplicationController
before_action :set_transaction, only: [:show, :edit, :update, :destroy]
def index
#report = Report.find(params[:report_id])
#transaction = #report.transactions
Rails.logger.info("********* report #{#report.inspect} ***********")
#transactions = #report.transactions
Rails.logger.info("********* transaction #{#transactions.inspect} ***********")
end
My route
resources :reports do
resources :transactions
end
The above post made me go back in and match all my case and that seems to be working.

Attribute is not saved to table

Rails 4.2.1
Ruby 2.1.5
I have the following helper method:
def parse_potential_followers(params)
t_id = TestSet.where(:test_name => params[:test_set][:test_name]).pluck(:id)
screen_names = get_screen_names
screen_names.each do |s|
potential_follower = PotentialFollower.new(
:screen_name => s,
:test_sets_id => t_id,
:status => 'new',
:slug => generate_slug([t_id, s])
)
logger.info("Test Set ID: #{t_id}")
potential_follower.save
end
end
The problem is that when I call this method, the test_sets_id is skipped when data is inserted in the table. The three other attributes are saved fine.
I verified through logger.info that t_id is valid.
All the attributes are defined in the potential_followers table.
I also have all the attributes in the potential_follower_params method in the potential_followers_controller.rb:
def potential_follower_params
params.require(:potential_follower).permit(:screen_name, :test_sets_id, :connections, :status,
:slug, :created_at, :updated_at)
end
What am I forgetting?
Answer:
t_id is an array (result of ActiveRecord query). If t_id is changed to t_id[0] when used in the hash, it will work fine
You get t_id by
t_id = TestSet.where(:test_name => params[:test_set][:test_name]).pluck(:id)
which is an array. Probably you should try to get a variable with integer type instead of array. If your test_sets_id is an integer, the value in array won't be saved.
My guess is the data type is different. Maybe you are trying to save string as an integer?

Outputting a serialized object in Rails

In Rails 2.3.6 I'm storing some serialized data in a database field.
My "feed_event.data" field in my database is stored as text and is (for example) equal to:
{:post=>{:pic=>"http://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpg", :name=>"Un’istruzione perfetta", :id=>1995, :authors=>"Delilah"}, :user=>{:pic=>"http://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPG", :name=>"Luci!", :id=>537}}
Now I need to output this field as a string (exactly as it is in the database), but when I ask:
puts feed_event.data
outputs:
postpichttp://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpgnameUn’istruzione perfettaid1995authorsDelilahuserpichttp://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPGnameLuci!
Why?
How can I output it as a yaml string?
UPDATE
In order to create it I have this in my FeedEvent model:
class FeedEvent < ActiveRecord::Base
has_many :user_feed_events, :dependent => :destroy
has_many :users, :through => :user_feed_events
serialize :data
end
And in order to create a new FeedEvent element I do:
feed = FeedEvent.create(:event_type => "comment #{commentable_type}", :type_id => id, :data => {:user => {:id => user.id, :name => user.name, :pic => user.avatar.url(:thumb)}, :comment => {:id => id, :body => body, :commentable_id => commentable_id, :commentable_type => :commentable_type, :commentable_name => commentable.name}})
UPDATE #2
following nzifnab's hint I used the .to_yaml method, but what Rails outputs in this case is:
data: "--- \n:post: \n :pic: http://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpg\n :authors: Delilah\n :name: \"Un\\xE2\\x80\\x99istruzione perfetta\"\n :id: 1995\n:user: \n :pic: http://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPG\n :name: Luci!\n :id: 537\n"
Also commenting "serialize :data" in the model outputs the same.
Thanks,
Augusto
When you call feed_data.data rails has automatically de-serialized your string. You could print it out like this:
feed_data.data.inspect to get the ruby hash representation as a string, but since it's already de-serialized it for you do you need to do anything else?
you can call everything on it like feed_data.data[:post][:pic]
I'm not sure what method you can use to grab the raw serialized string from the record, but usually you don't need to.
By default, serialization is made in a Hash.
Simply loop it to display it's content:
<% feed_event.data.each do |key, value| %>
<%= "#{key}: #{value}" %>
<% end %>
I'm just unsure about nesting level here but you've got the idea.
as you mentioned in your Update, the right way to do this is to put "serialize :data" in your model.
Then, you can access the data attribute as a Hash, that's the default, and it gets automatically persisted when you save your object.
Important Note:
One important thing for this to work is that you define the database field as text or string -- not as a binary field -- otherwise this will not work correctly!

How can I pass multiple attributes to find_or_create_by in Rails 3?

I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])
There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.
UPDATE:
Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.
product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")
The error methods was:
no such keys: property_id, value
I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:
product.product_properties.find_or_create_by_property_id_and_value(1, "X")
And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.
So I guess you get a down vote if you miss something on the internet?
If you want to search by multiple attributes, you can use "and" to append them. For example:
productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])
There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.
Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:
http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang
This applies to Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:
With single query parameter:
productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )
or extended with multiple parameters:
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )
Would work with:
conditions = { :product_id => product.id,
:property_id => property.id,
:value => d[descname] }
pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions)
In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.
You can also do something like:
User.create_with(
password: 'secret',
password_confirmation: 'secret',
confirmation_date: DateTime.now
).find_or_create_by(
email: 'admin#domain.com',
admin: true
)
If you need to create the user with some attributes, but cannot search by those attributes.
You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.
product_property_attrs = { product_id: product.id,
property_id: property.id,
value: d[descname] }
product_property = ProductProperty.where(product_property_attrs).first_or_create
I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.
ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
product.id, property.id, d[descname])

Resources