wicked gem - add steps dynamically based on previous selections - ruby-on-rails

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.

Related

Get next/previous record in Collection by Date in Rails (and handle multiple dates on the same day)

I'm having trouble trying to isolate the next/previous record in a collection. I'm self-taught and relatively new to Rails/coding.
I have a Goal class which has many GoalTasks.
GoalTask has taskduedate. I want to be able to cycle next/previous on the goal_tasks, based on their taskduedate.
The issue is that a task due date is just when the task is due to be completed, but it can be set at any time and may not be in sequential order so that I don't know what else to order it by to correctly cycle through it.
I have created an array of goal_tasks to identify which one is currently being viewed (e.g. Task: 3/20), so I could use that to go to the next one, I think there might be a solution here, but it feels wrong to handle it in the view/controller?
I've tried the below solution from stackoverflow, but it doesn't handle the fact that I have multiple goal_tasks due on the same day, if I click next it just goes to the next day that goal_tasks are due. e.g. if I have three tasks due today and I'm on the first one and click next, it will just skip over the other two for today.
I then tried to add the >= (displayed below) to try and pull the next task (including those on the same day), and I've tried to ignore the current task by doing where created_at is not the same as the current goal_task and where.not, but I haven't managed to successfully get it to cycle the way I want it to, and I imagine there's a better solution.
GoalTasksController:
def show
#all_tasks_ordered_due_date_desc = #goal.goal_tasks.order('taskduedate ASC', 'id ASC')
end
show.html.erb:
Task: <%= #all_tasks_ordered_due_date_desc.find_index(#goal_task) +1 %> /
<%= #goal.goal_tasks.count%>
GoalTask.rb
scope :next_task, lambda {|taskduedate| where('taskduedate >= ?', taskduedate).order('id ASC') }
scope :last_task, lambda {|taskduedate| where('taskduedate <= ?', taskduedate).order('id DESC') }
def next_goal_task
goal.goal_tasks.next_task(self.taskduedate).first
end
Thanks
I used the method found here: Rails 5: ActiveRecord collection index_by
Which meant adding a default scope and changing GoalTask.rb to:
default_scope { order('taskduedate ASC') }
def next_goal_task
index = goal.goal_tasks.index self
goal.goal_tasks[index + 1]
end
def last_goal_task
index = goal.goal_tasks.index self
goal.goal_tasks[index -1]
end

How to combining two search parameters in Rails

I am just getting into Rails and want to preform a simple search FROM destination TO destination. I have the basic search functionality working and connected to the database but when I choose FROM and TWO the output is combined and I get all transports FROM the specific text and all transports TO the specific text. I want to filter them and only have the ones that go FROM TO.
Here is what I have so far.
class Transport < ApplicationRecord
def self.searchfrom(searchfrom)
where("strtloc LIKE ?", "%#{searchfrom}%")
end
def self.searchto(searchto)
where("endloc LIKE ?", "%#{searchto}%")
end
end
I basically want to add AND in between the two parameters but I am stuck.
This is my transports_controller.rb
if params[:searchfrom]
#transports = Transport.searchfrom(params[:searchfrom])
else
#transports = Transport.all
end
if params[:searchto]
#transports = Transport.searchto(params[:searchto])
else
#transports = Transport.all
end
I also want the ability to only select FROM. So I cant really do a simple AND statement. But one at a time.
Any help apreciated!
Please check this answer:
Ruby on Rails: Search Form - multiple search fields
You should find this is related to you problem.
You can do either of the following:
#transports = Transport.where("strtloc LIKE ? AND endloc LIKE ?", "#{params[:searchfrom]}%", "#{params[:searchto]}%")
It'll do an AND operations between the two parameters.
or
#transports = Transport.where("strtloc LIKE ?", "#{params[:searchfrom]}%")
# first search for start locations
#transports = #transports.where("endloc LIKE ?", "#{params[:searchto]}%")
# then search for end locations in the result of start location.
Also check: HERE

How can I search a table based on multiple search criteria without require said criteria?

I have created a table that houses product brochures. I would like to allow the user to search the table based on three drop down menus (product type, carrier, concept). I have created the menus and the controller currently passes through the params correctly. If the user search by all 3 criteria, the search displays correctly. If the user chooses only one or two search options however, the result is always nothing. I understand why this is happening (the model is trying to search by all three criteria and when one is missing, it simply searches nil in that field), however I'm sure the correct solution. Please help! My model is below...thank you
def self.search (search_product_type, search_carrier, search_concept)
if search_product_type.blank? && search_carrier.blank? && search_concept.blank?
scoped
else
Ad.where(carrier_id: search_carrier)
.where(product_type_id: search_product_type)
.where(concept: search_concept)
end
end
As #Deefour said in the comments you should use a gem. But, you can also make something like this:
def self.search (search_product_type, search_carrier, search_concept)
if search_product_type.blank? && search_carrier.blank? && search_concept.blank?
scoped
else
tmp = {}
if search_carrier.present?
tmp[:carrier_id] = search_carrier
end
if search_product_type.present?
tmp[:product_type_id] = search_product_type
end
if search_carrier.present?
tmp[:concept] = search_concept
end
Ad.where(tmp)
end
end

Rails: Previous and Next record from previous query

My app has photos, and users can search for photos that meet certain criteria. Let's say a user searches for photos by tag, and we get something like this:
#results = Photo.tagged_with('mountain')
Now, #results is going to be a standard activerecord query with multiple records. These would be shown in a grid, and a user can then click on a photo. This would take the users to the photos#show action.
So, lets say the user searches for something and the app finds 5 records, [1,2,3,4,5], and the user clicks on photo #3.
On the photo#show page I'd like to be able to show a "Next Photo", "Previous Photo", and "Back to Search".
The only other constraint is, if the user browses to a photo directly (via another page or a bookmark etc) there wouldn't be a logical "next" and "previous" photo since there wasn't a query that led them to that photo, so in that case the template shouldn't render the query-related content at all.
So, I have been thinking about how to do this kind of thing and I don't really have a lot of good ideas. I suppose I could do something like store the query in session to be able to go back to it, but I don't know how to find the photos that would have shown up to the left and right of the selected photo.
Does anyone have any examples of how to do this kind of thing?
So, after much trial and error, here is what I came up with:
In my Photo model:
# NEXT / PREVIOUS FUNCTIONALITY
def previous(query)
unless query.nil?
index = query.find_index(self.id)
prev_id = query[index-1] unless index.zero?
self.class.find_by_id(prev_id)
end
end
def next(query)
unless query.nil?
index = query.find_index(self.id)
next_id = query[index+1] unless index == query.size
self.class.find_by_id(next_id)
end
end
This method returns the next and previous record from a search or a particular folder view by accepting an array of those records ids. I generate that ID in any controller view that creates a query view (ie the search page and the browse by folders page):
So, for instance, my search controller contains:
def search
#search = #collection.photos.search(params[:search])
#photos = #search.page(params[:page]).per(20)
session[:query] = #photos.map(&:id)
end
And then the photo#show action contains:
if session[:query]
#next_photo = #photo.next(session[:query])
#prev_photo = #photo.previous(session[:query])
end
And lastly, my view contains:
- if #prev_photo || #next_photo
#navigation
.header Related Photos
.prev
= link_to image_tag( #prev_photo.file.url :tenth ), collection_photo_path(#collection, #prev_photo) if #prev_photo
- if #prev_photo
%span Previous
.next
= link_to image_tag( #next_photo.file.url :tenth ), collection_photo_path(#collection, #next_photo) if #next_photo
- if #next_photo
%span Next
Now it turns out this works great in regular browsing situations -- but there is one gotcha that I have not yet fixed:
Theoretically, if a user searches a view, then jumps to a photo they've generated a query in session. If, for some reason, they then browse directly (via URL or bookmark) to another photo that was part of the previous query, the query will persist in session and the related photos links will still be visible on the second photo -- even though they shouldn't be on a photo someone loaded via bookmark.
However, in real life use cases this situation has actually been pretty difficult to recreate, and the code is working very well for the moment. At some point when I come up with a good fix for that one remaining gotcha I'll post it, but for now if anyone uses this idea just be aware that possibility exists.
Andrew, your method not universal and dont give guaranteed right result. There is better way to do this.
In your model:
def previous
Photo.where('photos.id < ?', self.id).first
end
def next
Photo.where('photos.id > ?', self.id).last
end
And in views:
- if #photo.previous
= link_to 'Previous', #photo.previous
- if #photo.next
= link_to 'Next', #photo.next
A gem I wrote called Nexter does it for you.
You pass it an AR Scope combination (aka ActiveRelation) plus the current Object/Record and Nexter will inspect the order clause to build the sql that will fetch the before/previous and after/next records.
Basically it looks at the ActiveRelation#order_values in order(a, b, c) and comes out with :
# pseudo code
where(a = value_of a AND b = value of b AND c > value of c).or
where(a = value_of a AND b > value of b).or
where(a > value of a)
That's only the gist of it. It also works with association values and is clever with finding the inverse values for the previous part. To keep the state of your search (or scope combination) you can use another lib like siphon, ransack, has_scope etc...
Here's a working example from the README
The model :
class Book
def nexter=(relation)
#nexter = Nexter.wrap(relation, self)
end
def next
#nexter.next
end
def previous
#nexter.previous
end
end
The controller
class BookController
before_filter :resource, except: :index
def resource
#book_search = BookSearch.new(params[:book_search])
#book ||= Book.includes([:author]).find(params[:id]).tap do |book|
book.nexter = siphon(Book.scoped).scope(#book_search)
end
end
end
The view :
<%= link_to "previous", book_path(#book.previous, book_search: params[:book_search]) %>
<%= link_to "collection", book_path(book_search: params[:book_search]) %>
<%= link_to "next", book_path(#book.next, book_search: params[:book_search])
```
You could take a look at what done in ActsAsAdjacent:
named_scope :previous, lambda { |i| {:conditions => ["#{self.table_name}.id < ?", i.id], :order => "#{self.table_name}.id DESC"} }
named_scope :next, lambda { |i| {:conditions => ["#{self.table_name}.id > ?", i.id], :order => "#{self.table_name}.id ASC"} }
Essentially, they're scopes(pre Rails 3 syntax) to retrieve records that have IDs lesser/greater than the ID of the record you passed in.
Since they're scopes, you can chain previous with .first to get the first item created before the current item, and .tagged_with('mountain').first to get the first such item tagged with 'mountain'.

How to insert predictable IDs for testing using ActiveRecord

I am trying to do some Cucumber testing like the following:
Given I am logged in as admin
When I go to the article page
Then I should see "Edit Article"
And I should see "Article Title"
where the path to the article page is "articles/1" or some known id.The problem is that when I insert my data using my step definition
Article.create(:name => "Article Title")
I can't know ahead of time what the id will be when the record is inserted.
Essentially, I either need
a) to be able to insert predictable ids. I've tried the method mentioned in the answer of this question but it doesn't seem to work for me - it always inserts a different id.
or
b) write my cucumber steps in such a way that I can pass in a parameter for my Article id
All Cucumber tutorials I've seen sort of gloss over this type of scenario - it's always going to the list of all Articles or whatever, never a specific detail page.
Any help would be appreciated. Thanks!
How about navigating to the article page in the way a user would do it? Your step
When I go to the article page
could be changed to
When I go to the page for the "Article Title" article
and your step definition goes to an article overview, finds a link with "Article Title" and navigates to the page that way.
You get more test coverage, and don't have to worry about IDs.
There are two ways to create predictable ID values. The first is to manually set the ID on create:
model = Model.new(options)
model.id = DESIRED_ID
model.save
The catch here is that options passed to either new, create, or update_attributes will not affect the id value which is why the call to model.id is, unfortunately, required.
An alternative is to reset the whole table before running your tests. This can be altered to account for fixtures if required. For example:
class Model < ActiveRecord::Base
def self.reset!
connection.execute("DELETE FROM #{table_name}")
connection.execute("ALTER TABLE #{table_name} AUTO_INCREMENT=1")
end
end
This will set the ID of the first row inserted to be a predictable 1.
How about making "Go to the Article Page" go to "/articles/#{Article.first.id}"
This is what I use:
# feature
Scenario: creator archives fragment
When fragment 1 exists
And I am the fragment owner
And I am on fragment 1
#steps
def create_fragment(attribs)
force_id = attribs[:force_id]
attribs.delete(:force_id) if force_id
fragment = Fragment.create!({ :owner_id => #current_user.id, :originator_id => #current_user.id}.merge(attribs))
if force_id
fragment.id = force_id.to_i
fragment.save!
end
fragment
end
When /^fragment (.*) exists$/ do |frag|
#fragment = Fragment.find_by_id(frag)
#fragment.destroy if #fragment
#fragment = create_fragment(:force_id => frag.to_i,:description => "test fragment #{frag}",:narrative => "narrative for fragment #{frag}")
end
# paths.rb
when /fragment (.*)/
fragment_path($1)
Have fun.

Resources