Bypass activity output to activity in switch case - elsa-workflows

I like the idea of activity inputs and outputs and prefer them over setting values in the workflow context. However I am now stuck with this approach when using switch cases. My workflow looks like this:
builder
.ReceiveHttpPostRequest<ImportProducts>("/import")
.Then<ExtractHttpRequestBodyActivity>()
.Then<GetShopDataActivity>()
.Switch(cases => cases
.Add(
context => context.GetInput<ShopData>()!.ShopType == "ShopType1",
#case => #case
.Then<GetShopType1ProductsActivity>()
.Then<FilterShopType1ProductsActivity>()
.Then<ConvertShopType1ProductsActivity>()))
.Then<DebugActivity>();
Without the switch case the input of GetShopType1ProductsActivity is the output of GetShopDataActivity, but using switch case the input is null.
Is there a way to bypass the output to the first activity of the case?
Edit: I'm using Elsa 2.0.0-preview7.1545

There is no way to bypass Elsa form sending activity output automatically to another activity as input.
That being said, it is up to you whether or not you use output/input.
What you can do is this:
Specify a name for GetShopDataActivity (using .WithName). E.g. "GetShopDataActivity1"
Use the output of GetShopDataActivity Directly as input for GetShopType1ProductsActivity.
To use output from a named activity, use the following code:
context.GetOutputFrom<ShopData>("GetShopDataActivity1");
An alternative approach is to store the output from GetShopDataActivity in a variable, then you can use that variable from other activities.
Tip: when developing custom activities, try to expose their inputs. For example, if the GetShopType1ProductsActivity needs the output from GetShopDataActivity, instead of reading input directly from the activity context within that activity, consider exposing the necessary data using an "input" property, like so:
[ActivityInput] public ShopData ShopData { get; set; }
Then you can update your workflow as follows:
builder
.ReceiveHttpPostRequest<ImportProducts>("/import")
.Then<ExtractHttpRequestBodyActivity>()
.Then<GetShopDataActivity>().WithName("GetShopDataActivity1")
.Switch(cases => cases
.Add(
context => context.GetInput<ShopData>()!.ShopType == "ShopType1",
#case => #case
.Then<GetShopType1ProductsActivity>(a => a.Set(x => x.ShopData, context => context.GetOutputFrom<ShopData>(""GetShopDataActivity1)))
.Then<FilterShopType1ProductsActivity>()
.Then<ConvertShopType1ProductsActivity>()))
.Then<DebugActivity>();
This could be cleaned up a bit by adding a couple of extension methods on your GetShopType1ProductsActivity class (more specifically, on ISetupActivity<GetShopType1ProductsActivity> called e.g. WithShopData.
Additionally, if you need the output from GetShopDataActivity in multiple places (like you do already in at least two places), consider creating a an extension method that provides this information.
For example:
public static class ShoppingWorkflowExtensions
{
public static ShopData GetShopData(this ActivityExecutionContext context) => context => context.GetOutputFrom<ShopData>(""GetShopDataActivity1)!;
}
You could then update your workflow like this:
builder
.ReceiveHttpPostRequest<ImportProducts>("/import")
.Then<ExtractHttpRequestBodyActivity>()
.Then<GetShopDataActivity>().WithName("GetShopDataActivity1")
.Switch(cases => cases
.Add(
context => context.GetShopData().ShopType == "ShopType1",
#case => #case
.Then<GetShopType1ProductsActivity>(x => x.WithShopData(context => context.GetShopData()))
.Then<FilterShopType1ProductsActivity>()
.Then<ConvertShopType1ProductsActivity>()))
.Then<DebugActivity>();

Related

Using ReactiveUI/DynamicData, How to observe changes to properties of items in a SourceList

Each item in the list implements ReactiveObject so I've tried using item.WhenAnyValue().Subscribe() on each individual item before adding it to the SourceList. While this works, it has terrible performance and isn't really practical for my SourceList of 40000 items. Is there any way to observe the SourceList once for changes to properties of any items in the list?
DynamicData provides several extensions to IObservable<IChangeSet<T>> which you can use to bind to all of the items in your SourceList. All of these will gracefully handle added/removed items from your list.
WhenValueChanged
This is basically the equivalent of WhenAnyValue but for lists and is probably what you're looking for. This will return an observable of the value when a target property has changed (and optionally when initialized). Example:
sourceList
.Connect()
.WhenValueChanged(item => item.property)
.Subscribe(newPropertyValue => { /* Do stuff */ }
WhenPropertyChanged
Similar to ObservableForProperty, this will return an observable of the value of target property and its parent object, when it has changed (and optionally when initialized). Example:
sourceList
.Connect()
.WhenPropertyChanged(item => item.property)
.Subscribe(change => { /* Do stuff with change.Sender and change.Value */ }
MergeMany
The most general option, MergeMany allows you to combine observables created or retrieved from each of your list items. For example, to accomplish something similar to WhenValueChanged you could do:
sourceList
.Connect()
.MergeMany(item => item.WhenAnyValue(x => x.property))
.Subscribe(newPropertyValue => { /* Do stuff */ }

Symfony2: How do i validate an object/array with its constraints in another object/array?

It's built dynamically, annotations are not an option. I have the constraints in an array.
It seems you need to manually construct the validator but I get stuck fast on the point where you need to construct a MetaDataFactory that needs an loader, but all the loaders have the job of loading meta data from a file.. I dont think I'm doing it right.
Simply said:
I have an array/object with keys/values. I want them validated by another array that contains the constraints.
It's not possible to iterate over the keys and call validate on each and every one of them, since some rely on the context values from other keys.
$array = [
'key1' => 'abc',
'key2' => 'def',
];
$constraints = [
'key1' => new All([
new Constraint..,
new CallbackConstraint.., // <- this one checks key2 for its value
]),
'key2' => new NotBlank
];
I can also build one array containing both.
Againt, the object/array is built dynamically, i cant validate the container itself. It's about the keys inside it that are definitions on its own.
So what i basically would want is:
$validator->validate($array, $constraints);
I can imagine you need a custom MetaDataFactory / Loader / Context class or something, the problem is simply that the callback validator needs to access $this->getRoot() to get to the other values
Nevermind that, you would need something like a CallbackLoader in which you create your own properties => constraints mapping. But the LoaderInterface requires a concrete implementation of ClassMetaData, which on his own has no way of dealing with arrays or ArrayObjects, unless you have methods/properties on that ArrayObject. But--since my container is built dynamically, i can't have that. Method methods are not an option because of the property/method_exists calls.

Bind to IReactiveCommand in view code behind

I have my ViewModel which has an ErrorCommand. I wish to subscribe to this in my view code behind so that any time it is called I can display an error message which is passed like so:
ErrorCommand.Exectute("Error occured")
In the view:
this.WhenAny(view => x.ViewModel.ErrorCommand, x => x.Value).Subscribe(error => DisplayError(error));
This code doesn't actually work but hopefully shows what I'm trying to acheive. How would I do this correctly?
I understand I could use the MessageBus, but I also have a similar scenario where the MessageBus wouldn't be appropriate.
There's a method specifically for this scenario:
this.WhenAnyObservable(x => x.ViewModel.ErrorCommand).Subscribe(x => /* ... */);
will do what you expect and will avoid the null refs
this.WhenAny(view => x.ViewModel.ErrorCommand, x => x.Value).Subscribe(error => DisplayError(error));
Will only fire when you change the value of the ErrorCommand property.
What you want is this:
ViewModel.ErrorCommand.IsExecuting.Subscribe(x=> DisplayError(x));

Cannot form a select statement for query in silverlight

I want to do something like
from table1
where col5="abcd"
select col1
I did like
query_ = From g In DomainService.GetGEsQuery Select New GE With {.Desc = g.codDesc}
"This cause a runtime error, i tried various combinations but failed"
please help.
I'm assuming your trying to do this on the client side. If so you could do something like this
DomainService.Load(DomainService.GetGEsQuery().Where(g => g.codDesc == "something"), lo =>
{
if (lo.HasError == false)
{
List<string> temp = lo.Entities.Select(a => a.Name).ToList();
}
}, null);
you could also do this in the server side (which i would personally prefer) like this
public IQueryable<string> GetGEStringList(string something)
{
return this.ObjectContext.GE.Where(g => g.codDesc == something).Select(a => a.Name);
}
Hope this helps
DomainService.GetGEsQuery() returns an IQueryable, that is only useful in a subsequent asynchronous load. Your are missing the () on the method call, but that is only the first problem.
You can apply filter operations to the query returned using Where etc, but it still needs to be passed to the Load method of your domain context (called DomainService in your example).
The example Jack7 has posted shows an anonymous callback from the load method which then accesses the results inside the load object lo and extracts just the required field with another query. Note that you can filter the query in RIA services, but not change the basic return type (i.e. you cannot filter out unwanted columns on the client-side).
Jack7's second suggestion to implement a specific method server-side, returning just the data you want, is your best option.

DataMapper first_or_create always set certain field?

I'm using the following with datamapper to create/get a new user from my db:
user = User.first_or_create({:id => data['id']})
This gets the user with id = data['id'] or creates it if it doesn't already exist.
I want to know how to set other attributes/fields of the returned object regardless of whether it is a new record or existing?
Is the only way to do this to then call user.update({:field => value ...}) or is there a better way to achieve this?
Well, you could write it as one line:
(User.first_or_create(:id => data['id'])).update(:field => value)
with hashes for the parameters if you wish (or if you need to specify more than one); however, it's worth noting that this will only work if the model as specified by the first_or_create is valid. If :name were a required field, for instance, then this wouldn't work:
(User.first_or_create({:id => data['id'], :name => "Morse"})).update(:name => "Lewis")
as the creation in the first part would fail.
You could get around this by specifying the parameters needed for a new record with something like
(User.first_or_create({:id => data['id'], :name => "Morse"}, {:name => "Lewis"})).update(:name => "Lewis")
but this is unusually painful, and is difficult to read.
Also note that using first_or_create with an :id will attempt to create a model with that specific :id, if such a record doesn't exist. This might not be what you want.
Alternatively, you can use first_or_new. You can't call update on an object created using this, however, as the record won't exist (although I believe this might have worked in previous versions of DataMapper).
Just for anyone coming across this answer, User.first_or_create({:id => data['id']}) does NOT "get the user with id = data['id'] or creates it if it doesn't already exist." It actually gets the first record in the table and changes its id t0 data['id'].
To actually get the first record with that id, or create it if it doesn't exist, you need to use a where clause:
User.where(id: data['id]).first_or_create

Resources