I'm trying to add an attribute to my model so I can say something like: $model->path and then get back a url. So I've added the following to the model's constructor:
public function __construct($attributes = array()){
$this->path = url('img/' . $this->{'file-name'});
parent::__construct($attributes);
}
But if I run Model::first() then I get the following:
{
id: 25,
text: "A lovely file",
file-name: "file.jpg",
created_at: "2016-02-12 11:44:37",
updated_at: "2016-02-12 11:44:37"
},
You'll notice that there is no path attribute. Am I doing something very wrong?! I want to see:
{
id: 25,
text: "A lovely file",
file-name: "file.jpg",
path: "http://myapp.app:8000/img/file.jpg",
created_at: "2016-02-12 11:44:37",
updated_at: "2016-02-12 11:44:37"
},
For the record, I have also tried $this->field = 'value'; and that didn't create an attribute either.
Read the documentation. You’d be better off using an accessor method.
public function getPathAttribute()
{
return url('img/'.$this->file_name);
}
You also shouldn’t be using dashes in column/property names, use the convention of underscores for separators.
Related
How would I hide unwanted data for a cleaner response in laravel when using with statements.
For example, let's pretend I have a single post. That post has comments, and then comments have multiple tags related to each comment.
So I have below to illustrate the idea.
I have a model called Post
I then have a relationship in Post model that has comments relationship, ie
public function PostComments()
{
return $this->hasMany('App\Models\Post\Comments', 'postId', 'id');
}
I also have a model called PostComments
I then have this relationship in there.
public function PostCommentsTags()
{
return $this->hasMany('App\Models\Posts\PostCommentTags', 'postCommentId', 'id');
}
Then in my controller I have
$post = Post::with(
'PostComments',
'PostComments.PostCommentsTags',
)->first();
This works wonderfully, I get a response like this.
{
title: 'This is a title',
description: 'Description',
comments:[
{
id: 1,
comment: 'this is a comment',
commentTags:[{
id: 1,
tagName: 'emotion',
tagValue: 'angry',
},
{
id: 10,
tagName: 'timeOfDay',
tagValue: 'morning',
}],
},
{
id: 2,
comment: 'this is a comment too',
commentTags:[{
id: 7,
tagName: 'emotion',
tagValue: 'happy',
},
{
id: 9,
tagName: 'timeOfDay',
tagValue: 'evening',
}],
},
{
id: 3,
comment: 'Too many comments now',
commentTags:[{
id: 12,
tagName: 'emotion',
tagValue: 'angry',
},
{
id: 14,
tagName: 'timeOfDay',
tagValue: 'evning',
}],
}
]
}
But I want to get rid of a lot of clutter, ie id. (the real thing is a lot more complex than this example, so has a lot of clutter and unneeded info)
So I have this below, which hides all the ids for comments, this works great!
$post->Comments->makeHidden(["id"]);
But how do I hide it for Comment tags too? (I want no ids in my json)
I have tried below, but it does not work? How do you access nested models when using the with statement. I can't seem to find anything to help.
$post->Comments->CommentsTags->makeHidden(["id"]);
Spent a bit to much time on this now so time to ask for help. Please help :)
Best wishes.
Argghh finally figured it out. If you want to remove unwanted data, you loop through it like this.
foreach($post->Comments as $comment){
$comment->CommentsTags->makeHidden(['id','created_at','updated_at']);
}
I guess its a nice way to have hidden attributes without having it the model.
In the code example below, if I exclude the description field the product is created successfully. With the description field in place I get a GraphQL error.
The code:
productCreate(
input: {
category: "Q2F0ZWdvcnk6MQ==", # Category ID
name: "Delete Me!", # Product name
productType: "UHJvZHVjdFR5cGU6MQ==", # Product Type ID
chargeTaxes: true,
weight: "0.3", # in Kg
rating: 5,
description: {text:"some text"}, # nope
}
)
The error:
graphql.error.base.GraphQLError: Argument \"input\" has invalid value {category: \"Q2F0ZWdvcnk6MQ==\", name: \"Delete Me!\", productType: \"UHJvZHVjdFR5cGU6MQ==\", chargeTaxes: true, weight: \"0.3\", rating: 5, description: {text: \"some text\"}}.",
"In field \"description\": Expected type \"JSONString\", found {text: \"some text\"}."
It is a string, for rich text it is using https://editorjs.io/
You can inspect the network tab in the dashboard to learn how APIs are being used
JSON string means providing a JSON text converted to a string. This can be achieved by escaping quotation marks within the JSON.
For example, this JSON
{ "text": "some text" }
can be converted to String as below:
"{\"text\":\"sometext\"}"
As you notice that the text encapsulated inside quotation marks, to be a valid String.
You can use https://jsontostring.com/ for the conversion
Your final code should be like this:
mutation {
productCreate(
input: {
category: "Q2F0ZWdvcnk6MQ==" # Category ID
name: "Delete Me!" # Product name
productType: "UHJvZHVjdFR5cGU6MQ==" # Product Type ID
chargeTaxes: true
weight: "0.3" # in Kg
rating: 5
description: "{\"text\":\"sometext\"}" # nope
}
){
product{
id
}
}
}
Sorting out the description syntax wasn't straightforward. From my question here:
Saleor on Github
I got this answer:
{
"id": "UHJvZHVjdDo3Mg==",
"description": "{\"blocks\":[{\"type\":\"paragraph\",\"data\":{\"text\":\"New description\"}}]}"
}
which I then implemented like this:
query = gql(
"""
mutation (
$slug: String!,
$product_title: String!,
$description: JSONString!,
$weight_grams: WeightScalar!,
)
{
productCreate(
input:{
category: "Q2F0ZWdvcnk6NQ==",
name: $product_title,
productType: "UHJvZHVjdFR5cGU6MQ==",
slug: $slug,
description: $description,
weight: $weight_grams,
}
)
{
errors {
field
message
}
product {
id
name
productType {
id
}
slug
}
}
}
"""
)
params = {
"product_title": str(product_title),
"description": '{"blocks":[{"type":"paragraph","data":{"text":"'
+ str(product_title + " (" + csv_product_id + ")")
+ '"}}]}',
"slug": str(csv_sku_code),
"weight_grams": str(weight_grams),
}
result = client.execute(query, variable_values=params)
This works well for us.
I running into what I assume is so simple I am just overthinking it. I am running the following commands in my Tinker session and it is working as expected:
$game = Games::find(1);
[!] Aliasing 'Games' to 'App\Models\Games' for this Tinker session.
=> App\Models\Games {#4386
id: 1,
user_id: 1,
title: "Test Game",
description: "This is a test of the game function",
max_players: 8,
deck: "default",
type: "Golf",
privacy: "Public",
current_player: null,
status: "pending",
deleted_at: null,
created_at: "2020-12-18 22:02:17",
updated_at: "2020-12-18 22:02:17",
}
>>> $game->players()->get();
=> Illuminate\Database\Eloquent\Collection {#4322
all: [
App\Models\User {#4384
id: 1,
name: "mark",
email: "test#test.com",
username: "user",
role: null,
email_verified_at: null,
created_at: "2020-12-18 22:02:08",
updated_at: "2020-12-18 22:02:08",
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4168
games_id: 1,
user_id: 1,
},
},
],
}
I have essentially placed that exact same code in my controller to pull a list of players in a game:
$game = Games::find($game);
$players = $game->players()->get();
and I am getting this when I hit the route:
Method Illuminate\Database\Eloquent\Collection::players does not exist.
I am confused why this wouldnt work in the controller if it works just fine in Tinker.
Thanks for the help!
The normal usage for find() is to pass an id, and it will return a model instance for that id. However, if you pass in an array, or an object that implements \Illuminate\Contracts\Support\Arrayable, it will return a Collection of all the found instances.
Your controller code is this:
$game = Games::find($game);
If the $game value passed into find() here is an array, it will return a Collection of all the models found using the ids in the array.
Another sneaky issue here is if the $game value passed into find() here is a model instance. In this case, this statement will return a Collection, because models implement the Arrayable contract mentioned above.
So, when you call find() and pass in a model, it will call toArray() on that model, attempt to find records for every value returned in the array, and return a Collection of all the records found.
In either case, $game is now a Collection, and you'll get an error when you attempt to call $game->players(), since the players() method does not exist on collections.
$game = Games::find($game);
If you pass a single id to find, it will return a Game model or null. But if you pass an array of ids (even if it's an array of length 1) or a Model (for some reason), it will return a Collection or null.
In your tinker session, try this and you'll have the same error thrown.
$game = Games::find(1);
$game = Games::find($game);
// or
$game = Games::find([1]);
$players = $game->players()->get();
I have an example model constructed like so:
const MyModelResponse = Joi.object().keys({
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
})
.description('example instance of MyModel with the unique ID present')
.label('MyModelResponse');
In my route, I want to make sure that my input parameter is validated against the id property of MyModeResponse like so:
validate: {
params: {
id: MyModelResponse.id,
},
options: { presence: 'required' },
failAction: failAction('request'),
}
This results in the schema validation error upon server start:
AssertionError [ERR_ASSERTION]: Invalid schema content: (id)
Is there a way to reference a key of a schema? Currently, I have to resort to either of the following:
Not referencing my MyModelResponse schema at all:
validate: {
params: {
id: Joi.number().integer().description('id of MyModel instance to get'),
},
options: { presence: 'required' },
failAction: failAction('request'),
}
Not using the Joi.object.keys() constructor by defining my model like this:
const MyModelResponse = {
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
}
The bottom approach allows me to reference the id property in my route but doesn't allow me to add descriptions and labels to my schema. I have tried using MyModel.describe().children.id in my route validation and I have made some attempts to deserialize the id object into a schema object to no avail.
Any suggestions would be appreciated.
Remove the keys() and use as follows
const MyModelResponse = Joi.object({
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
})
.description('example instance of MyModel with the unique ID present')
.label('MyModelResponse');
When performing queries I can go through Mongoid:
product_obj = Product.where(
_id: "58f876f683c336eec88e9db5"
).first # => #<Product _id: 58f876f683c336eec88e9db5, created_at: nil, updated_at: nil, sku: "123", name: "Some text", ...)
or I can circumvent it:
product_hsh = Product.collection.find( {
_id: BSON::ObjectId.from_string("58f876f683c336eec88e9db5")
}, {
projection: {
_id: 1,
name: 1
}
} ).first # => {"_id"=>BSON::ObjectId('58f876f683c336eec88e9db5'), "name"=>"Some text"}
I prefer to circumvent, because it performs better; I can limit which fields to get in the response.
My problem, however, is how to further work with the returned product. The response is a hash, not an object, so if I have to update it I need to pull it through Mongoid anyway, and thereby the performance gains are gone:
Product.find(product_hsh["_id"]).update_attribute(:name, "Some other text")
My question is: how do I update without first having to pull a Mongoid object?
You don't need to pull/fetch at all. You can just send the $set commands directly:
Product.where(id: product_hsh["_id"]).update_all(name: "Some other text")