Laravel Model Factory Error: Trying to get property of non-object - laravel-5.1

I'm trying to use a model factory to seed my database, but when I run it I get the error:
Trying to get property 'id' of non-object
Here is my code:
// TasksTableSeeder.php
factory(pams\Task::class, '2000', rand(1, 30))->create();
// ModelFactory.php
$factory->defineAs(pams\Task::class, '2000', function (Faker\Generator $faker) {
static $task_number = 01;
return [
'task_number' => $task_number++,
'ata_code' => '52-00-00',
'time_estimate' => $faker->randomFloat($nbMaxDecimals = 2, $min = 0.25, $max = 50),
'work_order_id' => '2000',
'description' => $faker->text($maxNbChars = 75),
'action' => '',
'duplicate' => '0',
'certified_by' => '1',
'certified_date' => '2015-11-08',
'status' => '1',
'created_by' => '1',
'modified_by' => '1',
'created_at' => Carbon\Carbon::now()->format('Y-m-d H:i:s'),
'updated_at' => Carbon\Carbon::now()->format('Y-m-d H:i:s'),
];
});
I've tried removing all variables from the model factory and using constants, but that doesn't fix it. I've tried pulling the data from the ModelFactory.php and put it directly into the TasksTableSeeder.php and it does work, however I was using constants and no variables.
I cannot for the life of me figure out what 'id' it's talking about.
I'm running Laravel v5.1

Your BaseModel is not exactly appropriate as it will force you to create hacks in order to run something like a unit test. It will be better that you have a flag in the BaseModel that you test for true or false before setting created_by and modified_by.
Also there's no guarantee there will be user_id '1' at any point in time, unless you actually create that User in your factory first, before creating the Task.
A way to fix your current setup is have a protected field like $enableAuthUpdates which is set to false or true by default. Then you can override the field in any of your derived models such as your Task model to prevent the creating/updating events from running.
Also it's important to make sure your factory has an actual user it is working with and create it if it doesn't exist.

I found the problem. At one stage I had implemented a BaseModel.php model that automatically inserts the current users ID when creating or updating the model. The seeder fails because there isn't a current user, so I had to add a check to see if there was a user logged in first.
Here is the code:
static::creating(function($model)
{
if(Auth::user())
{
$model->created_by = Auth::user()->id;
$model->modified_by = Auth::user()->id;
}
else
{
$model->created_by = '1';
$model->modified_by = '1';
}
});
static::updating(function($model)
{
if(Auth::user())
{
$model->modified_by = Auth::user()->id;
}
else
{
$model->modified_by = '1';
}
});
It's not super pretty, but it gets the job done :)

Related

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

Ruby / Mongoid - How to increment a value in a collection and return the new value

I'm updating some Ruby on Rails code that uses a pretty outdated version of Mongoid. I have the following line of code which gets the first document in a collection and increments the field nextid by 1, then returns the new value:
surveyid = SurveyId.first.safely.inc(:nextid, 1)
I've updated Mongoid to version 6.0.3, which no longet has a safely method. If I just use:
surveyid = SurveyId.first.inc(:nextid, 1)
It works, but inc doesn't return anything and I have no idea what the new value is.
What's the equivalent code in newer Mongoid versions? Thanks!
You can retrieve value like this.
surveyid = SurveyId.first.inc(:nextid, 1).nextid
I got this figured out. I found a gem that does exactly what I want called mongoid_auto_increment.
Now I can just add an auto-incrementing field to my collection and be done with it. Also, the source code for this Gem illustrates how to increment a value and get the new value, though I didn't really dig into it too much since I just decided to use the gem instead:
def inc
if defined?(::Mongoid::VERSION) && ::Mongoid::VERSION >= '5'
collection.find(query).find_one_and_update({ '$inc' => { number: #step } }, new: true, upsert: true, return_document: :after)['number']
elsif defined?(::Mongoid::VERSION) && ::Mongoid::VERSION >= '3'
collection.find(query).modify({ '$inc' => { number: #step } }, new: true, upsert: true)['number']
else
opts = {
"query" => query,
"update" => {"$inc" => { "number" => #step }},
"new" => true # return the modified document
}
collection.find_and_modify(opts)["number"]
end
end

Ruby Mongo Driver Projection Elemmatch

Following the code in http://www.w3resource.com/mongodb/mongodb-elemmatch-projection-operators.php I have set up a test database using the ruby mongodb driver.
For those following along at home, you first need to install the mongo driver as described at https://docs.mongodb.com/ecosystem/tutorial/ruby-driver-tutorial/#creating-a-client, then run the following commands.
client = Mongo::Client.new([ '127.0.0.1:27017'], :database => 'mydb')
test = client['test']
doc = {
"_id" => 1,
"batch" =>10452,
"tran_details" =>[
{
"qty" =>200,
"prate" =>50,
"mrp" =>70
},
{
"qty" =>250,
"prate" =>50,
"mrp" =>60
},
{
"qty" =>190,
"prate" =>55,
"mrp" =>75
}
]
}
test.insert_one(doc)
Insert all of the different docs created in the w3 tutorial.
If you look at example 2 in the w3 tutorial, the translated ruby find is:
test.find({"batch" => 10452}).projection({"tran_details" => {"$elemMatch" => {"prate" => 50, "mrp" => {"$gte" => 70}}}}).to_a
which returns the same result as in the example.
=> [{"_id"=>1, "tran_details"=>[{"qty"=>200, "prate"=>50, "mrp"=>70}]}, {"_id"=>3}, {"_id"=>4}]
My problem is that I would like to constrain the results with the constraints above (mrp gte 70 etc) while also specifying which fields are returned.
For instance, constraining only the tran_details that have a mrp gte 70, but in the results returned only include the prate field (or any subset of the fields).
I can return only the prate field with the query:
test.find({"batch" => 10452}).projection({"tran_details.prate" => 1}).to_a
I would like to combine the effects of the two different projections, but I haven't seen any documentation about how to do that online. If you string the two projections to each other, only the final projection has an effect.
To anyone out there --
The problem can be solved up to one element by using $elemMatch on projection. However, $elemMatch only returns the first result found. To return only parts of embedded documents multiple layers down that fit certain criteria, you need to use the aggregation framework.
test.find({
'tran_details.prate' => { '$gt' => 56 }
}).projection({
tran_details: {
'$elemMatch' => {
prate: { '$gt' => 56 }
}
},
'tran_details.qty' => 1,
'tran_details.mrp' => 1,
batch: 1,
_id: 0
}).to_a
To return only parts of embedded documents multiple layers down that fit certain criteria, you need to use the aggregation framework.
Here is example code
test.aggregate([{
'$match': {
'$or': [
{'batch': 10452}, {'batch': 73292}]}},
{'$project':
{trans_details:
{'$filter': {
input: '$tran_details',
as: 'item',
cond: {'$and':[
{'$gte' => ['$$item.prate', 51]},
{'gt' => ['$$item.prate', 53]}
]}
}
}
}
}]).to_a
If anyone sees this and knows how to dynamically construct queries in ruby from strings please let me know! Something to do with bson but still trying to find relevant documentation. Thanks -

Count by group and subgroup

I want to generate some stats regarding some data I have in a model
I want to create stats according to an association and a status column.
i.e.
Model.group(:association_id).group(:status).count
to get an outcome like
[{ association_id1 => { status1 => X1, status2 => y1 } },
{ association_id2 => { status1 => x2, status2 => y2 } }...etc
Not really bothered whether it comes out in as an array or hash, just need the numbers to come out consistently.
Is there a 'rails' way to do this or a handy gem?
Ok. Worked out something a little better, though happy to take advice on how to clean this up.
group_counts = Model.group(["association_id","status"]).count
This returns something like:
=> {[nil, "status1"]=>2,
[ass_id1, "status1"]=>58,
[ass_id2, "status7"]=>1,
[ass_id2, "status3"]=>71 ...etc
Which, while it contains the data, is a pig to work with.
stats = group_counts.group_by{|k,v| k[0]}.map{|k,v| {k => v.map{|x| {x[0][1] => x[1] }}.inject(:merge) }}
Gives me something a little friendlier
=> [{
nil => {
"status1" => 10,
"status2" => 23},
"ass_id1" => {
"status1" => 7,
"status2" => 23
}...etc]
Hope that helps somebody.
This is pretty ugly, inefficient and there must be a better way to do it, but...
Model.pluck(:association_id).uniq.map{ |ass| {
name:(ass.blank? ? nil : Association.find(ass).name), data:
Model.where(association_id:ass).group(:status).count
} }
Gives what I need.
Obviously if you didn't need name the first term would be a little simpler.
i.e.
Model.pluck(:association_id).uniq.map{ |ass| {
id:ass, data:Model.where(association_id:ass).group(:status).count
} }

sfWidgetFormDoctrineChoice: bug, error or what?

I'm using sfWidgetFormDoctrineChoice to build select elements in a form. This is how I'm using:
// Fill maquinaemisorid
$this->widgetSchema['maquinaemisorid'] = new sfWidgetFormDoctrineChoice(array('model' => 'SdrivingMaquina', 'add_empty' => 'Seleccione una Máquina', 'table_method' => 'fillChoice'));
// Fill idoperador
$this->widgetSchema['idoperador'] = new sfWidgetFormDoctrineChoice(array('model' => 'SdrivingOperador', 'add_empty' => 'Seleccione un Operador', 'table_method' => 'fillChoice'));
// Fill idturno
$this->widgetSchema['idturno'] = new sfWidgetFormDoctrineChoice(array('model' => 'SdrivingTurno', 'add_empty' => 'Seleccione un Turno', 'table_method' => 'fillChoice'));
For the first widget all is fine and values are taken from DB as should be but for the second one and the third is not working. As you may notice I'm calling a method fillChoice in all widgets table_method. This is the code for the three:
SdrivingMaquinaTable.class.php
public function fillChoice() {
$id_empresa = sfContext::getInstance()->getUser()->getGuardUser()->getSfGuardUserProfile()->getIdempresa();
return Doctrine_Core::getTable('SdrivingMaquina')->createQuery('a')->where('a.idempresa = ?', $id_empresa)->execute();
}
SdrivingOperadorTable.class.php
public function fillChoice() {
$id_empresa = sfContext::getInstance()->getUser()->getGuardUser()->getSfGuardUserProfile()->getIdempresa();
return Doctrine_Core::getTable('SdrivingOperador')->createQuery('a')->where('a.idempresa = ?', $id_empresa)->execute();
}
SdrivingTurno.class.php
public function fillChoice() {
$id_empresa = sfContext::getInstance()->getUser()->getGuardUser()->getSfGuardUserProfile()->getIdempresa();
return Doctrine_Core::getTable('SdrivingTurno')->createQuery('a')->where('a.idempresa = ?', $id_empresa)->execute();
}
The method is the same always just changes the table used for each class. I check the generated queries and all are fine and if I run each on phpMyAdmin for example more than one value is fetched altough in idoperador and idturno just the latest is rendered. This is a very rare behavior and I can't find the error or mistake so I need some help or advice here.
As an adition I tried this in SdrivingRegistrosEmisoresForm.class.php (this is where the widgets are being generated):
$id_empresa = $this->current_user->getSfGuardUserProfile()->getIdempresa();
$choices_operador = Doctrine_Core::getTable('SdrivingOperador')->createQuery('a')->where('a.idempresa = ?', $id_empresa)->execute();
$this->widgetSchema['idoperador'] = new sfWidgetFormSelect(array('choices' => $choices_operador));
And this way works fine but the id for each item fetched isn't right meaning:
id item real_id
0 Item1 2
1 Item2 16
2 Item4 3
I talked about this in this topic but didn't get any answer, any help?
As stated in sfWidgetFormDoctrineChoice, if option *table_method* returns a collection like your methods, it renders choices with options *key_method* and method. By default , they are respectively getPrimaryKey() and *__toString()*.
Can we check you schema.yml and the __toString() ?
As for your last example the option choices of sfWidgetFormSelect should be an array an not a *Doctrine_Collection*

Resources