I want to change attribute inside previously defined let to make my test works. I know it is hash in hash but none of my solutions works.
I've got several similar let but which looked like this
let(:second_data) do
{
'id' => second.id.to_s,
'type' => 'account',
'attributes' =>
{
'status' => 'new',
'created_at' => second.created_at.as_json,
'time_information' => second.credit.process.date_of_interest.as_json
}
}
end
At the end these lets are merged to one
let(:json_serialized_offers) do
{
'data' => [first_data, second_data, third_data],
'included' => first_included + second_included + third_included
}
end
Now I want to change status to expired in second_data which is nested in section data in :json_serialized_offers (as you see above).
I was trying to declere it once again, in right context, by
context "when status 'closed' passed " do
let(:json_serialized_offers) do
{
'data' => second_data { status: 'expire' }
}
end
# some logic
end
But nothing changed, is it possible to do so?
Just use a another "let" to set the status attribute.
Create a :status variable with a new let and change your first let like so:
let(:status) { 'new' } # <==== new let
let(:second_data) do
{
'id' => second.id.to_s,
'type' => 'account',
'attributes' =>
{
'status' => status # <==== set status using new variable
'created_at' => second.created_at.as_json,
'time_information' => second.credit.process.date_of_interest.as_json
}
}
end
Then, in the context where you need to change it, just redefine :status.
context "when status 'closed' passed " do
let(:status) { 'expired' }
it ...
end
This will redefine :status within this context, and will also change the status attribute in :second_data.
This strategy is great for setting deeply nested attributes, and it also makes it so that you only have to redefine the things that changed.
Related
I'm trying to parse out JSON data and create my own dictionary to show a subset of the data. The thing is, I'm noticing that my input data changes based on what is scanned (with nmap). Some elements might be an array value, whereas some might not. The combinations seem to be pretty broad.
For instance, here is the simplest input where only an IP address was found:
{
'host' => {
'address' => {
'addr' => '192.168.0.1'
},
'status' => {...}
}
}
But then, the IP and MAC address might be found:
{
'host' => {
'address' => [{
'addrtype' => 'ipv4',
'addr' => '192.168.0.1',
},{
'addrtype' => 'mac',
'mac' => '00:AA:BB:CC:DD:EE',
},
'status' => {...}
}]
}
Those are just a couple examples. Other variations I've seen:
`host.class` = Array
`address.class` = Hash
`host['status'].class` = Array
etc...
As I go through to parse the output, I am first checking if the element is an Array, if it is, I access the key/values one way, whereas if it's not an array, I essentially have to duplicate my code with a few tweaks to it, which doesn't seem very eloquent:
hash = {}
if hosts.class == Array
hosts.each do |host|
ip = if host['address'].class == Array
host['address'][0]['addr']
else
host['address']['addr']
end
hash[ip] = {}
end
else
ip = if hosts['address'].class == Array
hosts['address'][0]['addr']
else
hosts['address']['addr']
end
hash[ip] = {}
end
puts hash
end
In the end, I'm just trying to find a better/eloquent way to produce a hash like below, while accounts for the possibility that an element may/may not be an Array:
{
'192.168.0.1' => {
'mac' => '00:aa:bb:cc:dd:ee',
'vendor' => 'Apple',
'ports' => {
'80' => {
'status' => 'open',
'service' => 'httpd'
}
'443' => {
'status' => 'filtered',
'service' => 'httpd'
}
}
},
192.168.0.2 => {
...
}
}
If there a ruby method that I haven't run across yet that will make this more fluid?
Not really... but you can make it always an array eg by doing something like:
hosts = [hosts] unless hosts.is_a?(Array)
or similar... then just pass that to your now-non-duplicated code. :)
The 20 lines of code in your question can be reduced to a single line using Array#wrap instead of conditionals, and using Enumerable#map instead of Enumerable#each:
Array.wrap(hosts).map { |host| [Array.wrap(host['address']).first['addr'], {}] }.to_h
Now that's magic!
This is my first question, so I would also appreciate hints on how to ask properly.
So, In my Laravel app, I have a database table with users. For start, I wanted to have a model factory for it. So I took a standard code from laravel doc page:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
And I changed it to:
$factory->define(App\User::class,
function(Faker\Generator $faker) {
return [
'name' => $faker->name(),
'email' => $faker->safeEmail(),
'password' => bcrypt(str_random(10)),
'phone_number' => $faker->phoneNumber(),
'remember_token' => str_random(10),
'account_type' => 0,
];
});
So far, everything works. But I wanted it to be more sophisticated, and I decided to use more specific kind of Faker class, to generate Italian data. I changed it to:
$factory->define(App\User::class,
function(Faker\Generator $faker,
Faker\Provider\it_IT\PhoneNumber $fakerITPN,
Faker\Provider\it_IT\Person $fakerITPER,
Faker\Provider\it_IT\Internet $fakerITInt) {
return [
'name' => $fakerITPER->name(),
'email' => $fakerITInt->safeEmail(),
'password' => bcrypt(str_random(10)),
'phone_number' => $fakerITPN->phoneNumber(),
'remember_token' => str_random(10),
'account_type' => 0,
];
});
In seeder class I wrote:
factory(App\User::class)->create();
And then, after I used Artisan, command:
artisan migrate:refresh --seed -vvv
I get following error (just the head, for clearance):
[ErrorException]
Argument 2 passed to Illuminate\Database\Eloquent\Factory::{closure}() must be an instance of Faker\Provider\it_IT\PhoneNumber, array given
Exception trace:
() at /home/vagrant/php/housing/database/factories/ModelFactory.php:19
Illuminate\Foundation\Bootstrap\HandleExceptions->handleError() at /home/vagrant/php/housing/database/factories/ModelFactory.php:19
Illuminate\Database\Eloquent\Factory::{closure}() at n/a:n/a
call_user_func() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:130
Illuminate\Database\Eloquent\FactoryBuilder->Illuminate\Database\Eloquent\{closure}() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2308
Illuminate\Database\Eloquent\Model::unguarded() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:133
Illuminate\Database\Eloquent\FactoryBuilder->makeInstance() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:105
Illuminate\Database\Eloquent\FactoryBuilder->make() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:83
Illuminate\Database\Eloquent\FactoryBuilder->create() at /home/vagrant/php/housing/database/seeds/UsersTableSeeder.php:24
UsersTableSeeder->run() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Seeder.php:42
Clearly, there is something wrong with dependency injection, but I don't know what. I know, that in this case I could just manually create instances of classes I need, but I want to know, how to do it properly. Can anyone help?
If you take a look at the documention of faker # https://github.com/fzaninotto/Faker#localization, you'll see that you can simply assign the proper localization as a parameter to create.
In your case, just use:
Faker\Factory::create('it_IT');
You don't need to add more parameters in the anonymous function when you define the factory.
Edit:
Just to add on the issue on dependency injection. If you trace the source code, it does not do any dependency injection underneath.
$factory->define(...)
Only sets an array of definitions
public function define($class, callable $attributes, $name = 'default')
{
$this->definitions[$class][$name] = $attributes;
}
Calling
Faker\Factory::create();
or
factory(App\User::class)->create();
$factory->of($class)
calls "of" method that instantiate FactoryBuilder
(see lines 169-172 of Illuminate\Database\Eloquent\Factory.php)
public function of($class, $name = 'default')
{
return new FactoryBuilder($class, $name, $this->definitions, $this->faker);
}
after that, it chains "create" method of FactoryBuilder that calls "make" method which also calls "makeInstance"
protected function makeInstance(array $attributes = [])
{
return Model::unguarded(function () use ($attributes) {
if (! isset($this->definitions[$this->class][$this->name])) {
throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}].");
}
$definition = call_user_func($this->definitions[$this->class][$this->name], $this->faker, $attributes);
return new $this->class(array_merge($definition, $attributes));
});
}
Notice "call_user_func" inside "makeInstance", that is the one responsible for calling the anonymous function created as the 2nd argument to define (inside ModelFactory.php). It specifically pass only 2 arguments to the callable function, these are:
...$this->faker, $attributes);
Only 1 faker is passed on the first argument and an array of attributes on the 2nd argument (this is the one you saw on your ErrorException earlier)
That means you can only define your factory in this way:
$factory->define(App\User::class,
function (Faker\Generator $faker, $attributes=array()) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
If you really need other classes, you can initialize it outside of "define" and use it in the function like this:
$sampleInstance = app(App\Sample::class);
$factory->define(App\User::class,
function (Faker\Generator $faker, $attributes=array()) use($sampleInstance){
//...do something here
//...or process the $attributes received
//...or call a method like
$sampleData = $sampleInstance->doSomething();
return [
'someField' => $sampleData,
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
You can put this setting in register() of AppServiceProvider:
$this->app->singleton(\Faker\Generator::class, function () {
return \Faker\Factory::create('it_IT');
});
I have a Model called Example that accepts_nested_attributes_for NestedExample.
When I create a new Example Model I can also create NestedExamples:
params = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
Example.find_or_create_by_name params
This is all working fine. However, I rather than creating a new NestedExample every time, I would like Rails to perform a find_or_create_by_name on the NestedExample model, so that in the above situation, if there is already a NestedModel with a name of Nested Example 1, it will be used, rather than a new instance of NestedExample with the same name.
My current result:
params_1 = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
params_2 = { :name => 'Example 2', :nested_example_attributes => { :name => 'Nested Example 1' } }
example_1 = Example.find_or_create_by_name params_1
example_2 = Example.find_or_create_by_name params_2
puts example_1.nested_example.id == example_2.nested_example.id # Should be true
I have a method in my model Post like this:
def self.post_template
posts = Post.all
result = []
posts.each do |post|
single_post = {}
single_post['comment_title'] = post.comment.title
single_post['comment_content'] = post.comment.content
result << single_post
end
# return the result
result
end
In one of my rake tasks, I call the function:
namespace :post do
task :comments => :environment do
comments = Post.post_template
puts comments
end
end
In the console, the return value isn't an Array; instead, it prints all the hashes separated by a newline:
{ 'comment_title' => 'stuff', 'comment_content' => 'content' }
{ 'comment_title' => 'stuff', 'comment_content' => 'content' }
{ 'comment_title' => 'stuff', 'comment_content' => 'content' }
However, when I run this in my rails console, I get the expected behavior:
> rails c
> comments = Post.post_template
-- [{ 'comment_title' => 'stuff', 'comment_content' => 'content' },
{ 'comment_title' => 'stuff', 'comment_content' => 'content' }]
Needless to say, I'm pretty confused and would love any sort of guidance! Thank you.
EDIT:
Seems rake tasks simply print out arrays like this, but when I set the result of my array into another hash, it does not seem to maintain the integrity of the array:
namespace :post do
task :comments => :environment do
comments = Post.post_template
data = {}
data['messages'] = comments
end
end
I'm using Mandrill (plugin for Mailchimp) to create these messages and it throws an error saying that what I'm passing in isn't an Array.
I think that's just how rake prints arrays. Try this:
task :array do
puts ["First", "Second"]
end
Now:
> rake array
First
Second
I was wondering if it was possible to create new parent, children in a has many relationship, using rails nested forms.
Rails documentation clearly says that this works in a one to one relationship. Not sure if its the same in has many relationship.
For example:
If
params = {
:employee => {
:name => "Tester",
:account_attributes => {:login => 'tester'}
}
}
works as one to one relationship. So Employee.new(params) works fine. New employee, account are created.
Supposing I had
params = {
:employee => {
:name => "Tester",
:account_attributes => {
"0" => {:login => 'tester'},
"1" => {:login => 'tester2'}
}
}
}
Employee.new(params) doesnt work. It fails on child validations saying parent cant be blank.
Any help is appreciated. Thanks
Karen
The child_attributes= writer that comes with accepts_nested_attributes_for expects an array when it comes to one to many relationships.
This will create two accounts for the new employee
params = {
:employee => {
:name => "Tester",
:account_attributes => [
{:login => 'tester'},
{:login => 'tester2'}
]
}
}