How to represent "And" in Python behave step definition - bdd

For example i have following scenario in a feature file
Scenario: A Scenario
Given a precondition
When step 1
And step 2
Then step 3
In Ruby i can write stepdefinition for above scenario as following:
Given("a precondition") do
end
When("step 1") do
end
And("step 2") do
end
Then("step 3") do
end
I have to implement this using Python Behave and i am confused about And implementation annotation in stepdefinition for this, i did not find #and in the examples i referred.
#given("a precondition")
def given_implementation(context)
pass
#when("step 1")
def when_implementation(context)
pass
#which annotation to use for and??
def and_implementation(context)
pass
#then("step 3")
def then_implementation(context):
pass

And merely inherits from whatever the previous step was. From the docs,
So in your case, you'd want to change your step implementation to the following:
#given("a precondition")
def given_implementation(context)
pass
#when("step 1")
def when_implementation(context)
pass
#when("step 2") <--------------------- Changed to this!
def and_implementation(context)
pass
#then("step 3")
def then_implementation(context):
pass

Type #step instead of #when. And it will be universal. It means that the same step definition might be used with other keywords like given, when, then

Related

Can you pass a "next" back to the function that called the current function?

I have a series of nested each loops that iterate through a list of cards. These loops call out to other sub-functions that test if certain conditions are met in order to proceed.
def card_handler
cards.each do |card|
#some non-relevant code is here on my end
already_sent?
end
end
def already_sent?
# allows for checking if different emails have been sent on the same card
if list_action == 147
a_s_helper(p1_label)
elsif list_action == 146
a_s_helper(p2_label)
elsif list_action == 145
a_s_helper(p3_label)
end
end
def a_s_helper(label)
if card::card_labels.include? label
# if the card already has the label, I want to log the error and return all the way to the next card in the iteration
puts '\n Order info: \n id: #{id} \n Email already sent'
next
# doesn't work
else
real_id?
end
end
Like I say in my comment in a_s_helper, if the card already has the label, I want to log the error and return all the way to the next card in the iteration. I get an "Invalid next" error from the current setup.
Is there a way to return a next back to the parent function or loop?
next is only valid in the direct context of a loop. Once you call into a method, you are no longer directly in that loop context. You cannot use next to short-circuit the outer loop like this.
You have a couple of options:
Return statuses from your predicate functions (which is what you should do, from a predicate!) and short-circuit the loop based on those, or
Use Ruby's catch...throw construct (which is NOT its raise/rescue exception handler, but is instead something like a block-scoped GOTO statement)
Option 1: Returning statuses. This is the most appropriate method, IMO. Predicate methods (those ending in ?) should conventionally return a boolean and be idempotent (that is, should have no side effects, such as logging a statement). They are conventionally used to ask a yes/no question. Deciding what to do based on that question should ideally be outside of their scope.
def card_handler
cards.each do |card|
#some non-relevant code is here on my end
if already_sent?
puts '\n Order info: \n id: #{id} \n Email already sent'
next
end
end
end
def already_sent?
case list_action
when 145
a_s_helper(p3_label)
when 145
a_s_helper(p2_label)
when 147
a_s_helper(p1_label)
end
end
def a_s_helper(label)
card::card_labels.include? label
end
This causes your helpers to return a true or false value to your loop, which can decide to log a message and go to the next iteration.
Option 2: catch...throw
def card_handler
cards.each do |card|
# Put all your code that should nomally run inside the catch block. If
# the message :email_sent is thrown, then Ruby will zip up the stack and
# resume execution at the end of the block. This will skip any unexecuted
# code in the block, essentially terminating the execution.
catch :email_sent do
already_sent?
end
end
end
def already_sent?
# ...
end
def a_s_helper(label)
# ...
throw :email_sent if card::card_labels.include? label
# ...
end
You may be tempted to use option 2, since it requires less careful control over method construction, but it is perilously close to exceptions as flow control which are widely considered an antipattern (it's essentially a slightly more fancy GOTO, which is notorious for making code difficult to read and debug). If you can simply return a status from your helpers and decide whether or not to continue the loop based on that, you should do so.
I want to show how I ended up implementing the solution I got from #Chris-heald for future people who see this question. I made it a little more compact. This was the code I ended up using:
def card_handler
cards.each do |card|
real_id?
puts "real_id? : #{real_id?}"
next if !(real_id?)
needs_email?
puts "needs_email? : #{needs_email?}"
next if !(needs_email?)
get_email_info
end
end
def needs_email?
case list_action
when 147
!(card::card_labels.include? p1_label::id)
when 146
!(card::card_labels.include? p2_label::id)
when 145
!(card::card_labels.include? p3_label::id)
else
false
end
end
def real_id?
id != 0 ? true : false
end
def get_email_info
#more stuff
end

Rails Query a List for a CRON Job

I'm a complete novice with CRON jobs but I think I have that set up correctly.
Ultimately what I'm trying to do is send an email every day at 8:00 am to users (and a couple others) that have not logged in within the last 3 days, have not received the email, AND are marked as active OR temp as a status.
So from querying the db in console I know that I can do:
first = User.where(status: 'active').or(User.where(status: 'temp'))
second = first.where("last_login_at < ? ", Time.now-3.days)
third = second.where(notified: false)
That's not certainly clean but I was struggling with finding a contained query that grabbed all that data. Is there a cleaner way to do this query?
I believe I have my cron job set up correctly using a runner. I have whenever installed and in my schedule.rb I have:
every 1.day, at: '8:00 am' do
runner 'ReminderMailer.agent_mailer.deliver'
end
So under app > mailer I created ReminderMailer
class ReminderMailer < ApplicationMailer
helper ReminderHelper
def agent_reminder(user)
#user = user
mail(to: email_recipients(user), subject: 'This is your reminder')
end
def email_recipients(agent)
email_address = ''
email_addresses += agent.notification_emails + ',' if agent.notification_emails
email_addresses += agent.manager
email_address += agent.email
end
end
Where I'm actually struggling is where I should put my queries to send to the mailer, which is why I built a ReminderHelper.
module ReminderHelper
def applicable_agents(user)
agent = []
first = User.where(status: 'active').or(User.where(status: 'temp'))
second = first.where("last_login_at < ? ", Time.now-3.days)
third = second.where(notified: false)
agent << third
return agent
end
end
EDIT: So I know I could in theory do a chain of where queries. There's gotta be a better way right?
So what I need help on is: do I have the right structure in place? Is there a cleaner way to query this data in ActiveRecord for the CRON job? Is there a way to test this?
Try combining them together as if understand the conditions correct
Have not logged in within the last 3 days,
Have not received the email
Are marked as active OR temp as a status
User.where("last_login_at < ? ", 3.days.ago).
where(notified: false).
where(status: ['active', temp])
module ReminderHelper
def applicable_agents(user)
User.where("last_login_at < ? ", 3.days.ago).
where(notified: false).
where(status: ['active', temp])
end
end
You don't need to add/ assign them to array. Because this relation is already like an array. You can use .to_a if you need array. If you just want to iterate over them then users.each should work fine.
Update
class User
scope :not_notified, -> { where(notified: false) }
scope :active_or_temp, -> { where(status: ['active', 'temmp']) }
scope :last_login_in, -> (default_days = 3) { where("last_login_at < ?", default_days.days.ago) }
end
and then use
User.not_notified.active_or_temp.last_login_in(3)
Instead of Time.now-3.days it's better to use 3.days.ago because it keeps time zone also in consideration and avoids unnecessary troubles and failing test cases.
Additionally you can create small small scopes and combine them. More read on scopes https://guides.rubyonrails.org/active_record_querying.html

wicked gem - add steps dynamically based on previous selections

How it it possible to implement a wicked gem wizzard and add steps dynamically:
- first step => select country
- second step => select select city
- other step => display shops listsbased on the previous 2 selections.
It seems like we can add it like this in the controller:
before_action :set_steps
before_action :setup_wizard
...
private
def set_steps
if params[:flow] == "twitter"
self.steps = [:ask_twitter, :ask_email]
elsif params[:flow] == "facebook"
self.steps = [:ask_facebook, :ask_email]
end
end
But I wonder if it is possible not create a new array of steps but adding the new ones to the previous ones, e.g.:
self.steps << Shop.some_query_based_on_country_and_city
More of that, in all the examples you know exactly the step names and their content, so you have a page per step. How to do if the content is the same but there are à lot of identical content steps (for example questions to answer)?
Any idea ? Thank you.

Case insensitive XPath matching with custom function

Since XPath supports custom functions I created one to make it possible to match case insensitive:
class XpathFunctions
def case_insensitive_equals node_set, str_to_match
node_set.find_all do |node|
node.to_s.downcase == str_to_match.to_s.downcase
end
end
end
Testing with this page, however, returns these results:
agent = Mechanize.new
page = agent.get('http://www.angelettiauto.it/parcoveicoli.php').parser
page.xpath("//*[case_insensitive_equals(text(),'Audi')]", XpathFunctions.new).count
# => 1
The expected results would have been 4, because there are 4 Audis listed on the page and I need all of them.
This is of cause caused by using an exact match and not contains(), but I can't figure out where to inject it.
This can be achieved by modifying the case_insensitive_equals method like so:
def case_insensitive_equals node_set, str_to_match
node_set.find_all do |node|
node.to_s.downcase.include?(str_to_match.downcase)
end
end

Alternatives to nested conditionals Rails controller

I have a page that presents bits of information in a grid format. What shows up in each grid tile depends on:
Whether the user is logged in or not
What the user clicks on
Other factors
I'm using AJAX to send the controller what the user clicks on and grab fresh content for the tiles.
# Simplified pseudocode example
def get_tile_content
tile_objects = []
if current_user.present?
if params[:user_selected_content] == 'my stuff'
tile_objects = [... some model scope source here...]
elsif params[:user_selected_content] == 'new stuff'
tile_objects = [... some model scope source here...]
elsif params[:user_selected_content] == 'other stuff'
tile_objects = [... some model scope source here...]
end
else
tile_objects = [... some model scope source here...]
end
render :json => tile_objects.to_json || {}
end
Any ideas on how to approach this differently? I tried moving the complexity to models but I found it to be less readable and harder to figure out what is going on.
Looks like a decent case for a case statement (...see what I did there? ;P)
def get_tile_content
tile_objects = []
if current_user.present?
tile_objects = case params[:user_selected_content]
when 'my stuff' then Model.my_stuff
when 'new stuff' then Model.new_stuff
when 'other stuff' then Model.other_stuff
end
else
tile_objects = Model.public_stuff
end
render :json => tile_objects.to_json || {}
end
Sometimes case statements are necessary. Can't really say if that's the situation here since I can't see the larger design of your app, but this at least will clean it up a bit to fewer, easier to read lines, depending on your style preference.
You could wrap the case statement into its own method if you prefer, passing it the value of your parameter.
Another style point is that you don't typically use get_ at the beginning of ruby method names. It's assumed that .blah= is a setter and .blah is a getter, so .get_blah is redundant.

Resources