On Yii2 i have this code in ProjectQuery
class ProjectQuery extends \yii\db\ActiveQuery
{
public function init()
{
$this->andOnCondition(['deleted' => 0]);
parent::init();
}
Obviously the deleted condition must always apply, but there could be cases where this isn't true (for example an option for the user to see his deleted projects). How can i override this condition? Do i have to use something different from init() ?
(note, i want to apply this condition to all kind of queries normally, that's why i used init() on ProjectQuery and not the ProjectSearch class)
You can still use init() but to override the 0 you should bind a parameter.
public function init()
{
$this->andOnCondition('deleted = :deleted', [':deleted' => 0]);
parent::init();
}
So to create a query that only shows the deleted projects write something like this:
$query = Project::find()->addParams([':deleted' => 1]);
To show all projects, deleted and not deleted, you could add a function to the ProjectQuery object to modify it accordingly.
public function includeDeleted() {
$this->orOnCondition(['deleted' => 1]);
return $this;
}
And then write your query like:
$query = Project::find()->includeDeleted();
You can use onCondition() to override existing on conditions:
public function init() {
$this->andOnCondition('deleted = :deleted', [':deleted' => 0]);
parent::init();
}
public function includeDeleted() {
$this->onCondition(null);
// remove unused param from old ON condition
unset($this->params[':deleted']);
return $this;
}
You can use where() in the same way if you want to override conditions added by where(), andWhere and orWhere().
Assuming that you have a class Project where you have overwritten the find() method to return a ProjectQuery instance, the following might be another option. I also assume that you regularely query for undeleted items, and not so often but explicitly for all/deleted items.
Another option could be to add another method to the Project class and remove the default initialization in the ProjectQuery class.
class ProjectQuery extends \yii\db\ActiveQuery
{
public function init()
{
parent::init();
}
...
}
And:
class Project extends \yii\db\ActiveRecord {
...
public static function find()
{
return (new ProjectQuery(get_called_class()))
->andOnCondition(['deleted' => 0]);
}
public static function findAllProjects() // or find any better name for this
{
return new ProjectQuery(get_called_class());
}
}
Now, whenever you want to query explicitly all projects you would need to use this extra method Project::findAllProjects(). So in normal circumstances you won't have to remember that you have to modify the query in some way. No danger, that this could be forgotten.
It is still not 100% satisfying, since one could use find() and add ->andOnCondition(['deleted' => 1]) which would mean no records can be found. However, regarding security this is not so critical and the problem is found easily, I guess.
Related
A custom object that takes a parameter of (DocumentSnapShot documentsnapShot). also is an inner object from Firebase that retrieves a snapshot and set the values to my custom model also have its argument (DocumentSnapShot documentsnapShot). However, I wish to get the data from Firebase and pass it to my custom argument because mine takes multiple data not only Firebase. And it's not possible to iterate Firestore without an override.
Here's the code:
public UserSettings getUserSettings(DocumentSnapshot documentSnapshot){
Log.d(TAG, "getUserSettings: retrieving user account settings from firestore");
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
}
});
}
You cannot return something now that hasn't been loaded yet. Firestore loads data asynchronously, since it may take some time for this. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available. If you want to pass settings object to another method, just call that method inside onSuccess() method and pass that object as an argument. So a quick fix would be this:
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
yourMethod(settings);
}
One more thing to mention is that you don't need to set the those values to object that already have them. You are already getting the data from the database as an object.
So remember, onSuccess() method has an asynchronous behaviour, which means that is called even before you are getting the data from your database. If you want to use the settings object outside that method, you need to create your own callback. To achieve this, first you need to create an interface like this:
public interface MyCallback {
void onCallback(UserAccountSettings settings);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
myCallback.onCallback(settings);
}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(UserAccountSettings settings) {
Log.d("TAG", settings.getDisplay_name());
}
});
This is the only way in which you can use that object of UserAccountSettings class outside onSuccess() method. For more informations, you can take also a look at this video.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<UserAccountSettings> userSettingsMutableLiveData = new MutableLiveData<>();
public MutableLiveData<UserAccountSettings> getUserSettings(DocumentSnapshot documentSnapshot){
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
userSettingsMutableLiveData.setValue(settings);
}
});
return userSettingsMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserSettings().observe(this, new Observer<UserAccountSettings>() {
#Override
public void onChanged(UserAccountSettings userAccountSettings) {
//here, do whatever you want on `userAccountSettings`
}
});
I am trying to clean and refactor my service code which currently looks like this-
public void generateBalance(Receipt receipt) {
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) {
// do something to the receipt that's passed
} else if (receiptType.equals(Y)) {
// do something to the receipt
} else if (receipt.getRegion.equals(APAC) {
// call an external API and update the receipt
}....
...
// finally
dataStore.save(receipt);
Basically there's a bunch of conditionals that are in this main service which look for certain fields in the object that is being passed. Either it's the type or the region.
I was looking to use this design pattern- https://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html
However, I am not sure how this would work for a service class. Currently my REST handler calls this particular service. Also how can I do polymorphism for both the "receiptType" and "region"?
Is there a way I can just do all the updates to the receipt once in different services, then finally save the receipt at one location? (maybe a base class?) I am really confused on how to start. TIA!
If your classes should have the same behaviour, then it becomes pretty simple to use polymorpism. The pattern is called as Strategy. Let me show an example.
At first we need to use enum. If you do not have enum, then you can create a method which will return enum value based on your conditions:
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) // other
// code is omitted for the brevity
So enum will look like this:
public enum ReceiptType
{
Emea, Y, Apac
}
Then we need an abstract class which will describe behaviour for derived classes:
public abstract class ActionReceipt
{
public abstract string Do();
}
And our derived classes will look this:
public class ActionReceiptEmea : ActionReceipt
{
public override string Do()
{
return "I am Emea";
}
}
public class ActionReceiptY : ActionReceipt
{
public override string Do()
{
return "I am Y";
}
}
public class ActionReceiptApac : ActionReceipt
{
public override string Do()
{
return "I am Apac";
}
}
Moreover, we need a factory which will create derived classes based on enum. So we can use Factory pattern with a slight modification:
public class ActionReceiptFactory
{
private Dictionary<ReceiptType, ActionReceipt> _actionReceiptByType =
new Dictionary<ReceiptType, ActionReceipt>
{
{
ReceiptType.Apac, new ActionReceiptApac()
},
{
ReceiptType.Emea, new ActionReceiptEmea()
},
{
ReceiptType.Y, new ActionReceiptY()
}
};
public ActionReceipt GetInstanceByReceiptType(ReceiptType receiptType) =>
_actionReceiptByType[receiptType];
}
And then polymorpism in action will look like this:
void DoSomething(ReceiptType receiptType)
{
ActionReceiptFactory actionReceiptFactory = new ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}
UPDATE:
You can create some helper method which will return enum value based on
your logic of region and receiptType:
public class ReceiptTypeHelper
{
public ReceiptType Get(ActionReceipt actionReceipt)
{
if (actionReceipt.GetType().Equals("Emea"))
return ReceiptType.Emea;
else if (actionReceipt.GetType().Equals("Y"))
return ReceiptType.Y;
return ReceiptType.Apac;
}
}
and you can call it like this:
void DoSomething()
{
ReceiptTypeHelper receiptTypeHelper = new ReceiptTypeHelper();
ReceiptType receiptType = receiptTypeHelper
.Get(new ActionReceiptEmea());
ActionReceiptFactory actionReceiptFactory = new
ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}
I'm trying to create a simple feature to make the first action act like the second one.
public IActionResult GetMessage()
{
return "message";
}
public IActionResult GetMessageDataModel()
{
return new MessageDataModel("message");
}
First idea came to my mind was to extend SystemTextJsonOutputFormater, and wrap context.Object with my data model in WriteResponseBodyAsync, but the action is marked sealed.
Then I tried to override WriteAsync but context.Object doesn't have protected setter, either.
Is there anyway I can achieve this by manipulating OutputFormatter?
Or I have another option instead of a custom OutputFormatter?
for some reason they prefer every response in a same format like {"return":"some message I write.","code":1}, hence I want this feature to achieve this instead of creating MessageDataModel every time.
Based on your description and requirement, it seems that you'd like to generate unified-format data globally instead of achieving it in each action's code logic. To achieve it, you can try to implement it in action filter, like below.
public class MyCustomFilter : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
// implement code logic here
// based on your actual scenario
// get original message
// generate new instance of MessageDataModel
//example:
var mes = context.Result as JsonResult;
var model = new MessageDataModel
{
Code = 1,
Return = mes.Value.ToString()
};
context.Result = new JsonResult(model);
}
Apply it on specific action(s)
[MyCustomFilter]
public IActionResult GetMessage()
{
return Json("message");
}
I need to do a where condition on the code field from my intermediate table. My two models are;
class Agreement extends Eloquent {
protected $table = 'agreements';
public function clients(){
return $this->belongsToMany('Client', 'client_agreements')->withPivot('start_date', 'expire_date', 'code');
}
}
And;
class Client extends Eloquent {
public function agreements(){
return $this->belongsToMany('Agreement', 'client_agreements')->withPivot('start_date', 'expire_date', 'id', 'code');
}
}
My controller is currently;
public function show($code, $client_id)
{
//
$client = Client::with('agreements')->find($client_id);
$client_agreement = $client->agreements;
}
I think I need to expand the $client->agreements; code to include a where condition on the $code. I have tried so many different combinations but just keep returning the same Call to undefined method error.
I've tried things like;
$client_agreement = $client->agreements->where('code', '=', $code);
$client_agreement = $client->agreements->code->find($code);
$client_agreement = $client->agreements->pivot->code->find($code);
I always get the same error. I'm not that great on objects so maybe I'm looking at this all wrong. How is it done?
You need to access ->relation() not ->relation (which is dynamic property call):
$client_agreement = $client->agreements()
->wherePivot('code', '=', $code)
->first();
I have a constructor such as:
public AnalyticsController(ClassA classA, ClassB classB, bool isLiveEnvironment)
{
...
}
isLiveEnvironment is determined using a call to an existing static class such as:
MultiTenancyDetection.GetInstance().IsLive();
I would like to be able to make this call outside of the controller and inject the result into isLiveEnvironment. Is this possible? I can not see how this can be done.
You can accomplish this using WithConstructorArgument and using a callback:
kernel.Bind<AnalyticsController>()
.ToSelf()
.WithConstructorArgument("isLiveEnvironment", ctx => MultiTenancyDetection.GetInstance().IsLive() );
You can even achieve this more generally (but i would not really recommend binding such a generic type for such a specific use case):
IBindingRoot.Bind<bool>().ToMethod(x => MultiTenancyDetection.GetInstance().IsLive())
.When(x => x.Target.Name == "isLiveEnvironment");
Alternatively, if you need the same configuration value in several / a lot of classes, create an interface for it:
public interface IEnvironment
{
bool IsLive { get; }
}
internal class Environment : IEnvironment
{
public bool IsLive
{
get
{
return MultiTenancyDetection.GetInstance().IsLive();
}
}
}
IBindingRoot.Bind<IEnvironment>().To<Environment>();