redirecting back to different object - ruby-on-rails

I have a problem for redirecting back after an action.
My condition is this:
Client, Volunteer, and staff has many next of kin. After creating a new next of kin, I want to redirect back to edit page of a particular client/volunteer/staff.
my current solution is this.
For the link to add
<%= link_to new_admin_people_next_of_kin_path(source: source,
source_id: source_id),
class: 'js-btn-add btn btn-success btn-sm' do %>
Add New Next of Kin
<% end %>
where
source = :client/:staff/:volunteer
source_id = id(primary key) or the staff/volunteer/client
my new method
def new
#person = Person.new
#person.source = params[:source]
#person.source_id = params[:source_id]
end
later I will pass source and source_id as hidden parameter.
my people_controller create method(because next of kin is a person)
def create
if params[:source] == 'client'
#client = Client.find(params[:source_id])
#pnok = #client.people_next_of_kin.build
elsif params[:source] == 'volunteer'
#volunteer = Volunteer.find(params[:source_id])
#pnok = #volunteer.people_next_of_kin.build
elsif params[:source] == 'staff'
#staff = Staff.find(params[:source_id])
#pnok = #staff.people_next_of_kin.build
else
#pnok = PeopleNextOfKin.new
end
#person = #pnok.build_next_of_kin
Person.transaction do
#person.update_attributes(create_params)
#person.save(validate: false)
end
end
as you can see, it's not really clean and hardcoded. I have read on polymorphic path, but I can't really find a way to use that for my solution as I need to build a new next of kin first and I cannot pass in an object in link_to or redirect_to, and then there's also a problem whereby the next of kin is not saved yet in database, so I cannot use person.find.
any solution?

It's a little tough to see what you are after from your example. I would guess that you want to redirect back to the staff / volunteer / client page and there would be a Parent / Child relationship with a NOK.
However, it's unclear what your models look like. For example, you might be using single table inheritance and polymorphism because these are all "People", or you might have the relationships in your models. I think the solution depends on which path you take.
For example, you might use something like this:
def Client
has_many :noks
end
If you had that, you could build the empty :nok record and then redirect back to the :client, and let the Rails / ActiveRecord internals manage the relationship. For example, the Client #show page may have places to list all Next of Kins that enumerates all of the kin.
Summary: I think you are trying to do too much in your controller without using models the way that RoR supports.

Related

Ruby on Rails 5: Find index of post_id and display in view (post # of n)

I have a resource :posts, which I show one at a time in show.html.erb
Suppose I have ten posts, each with an :id going from 1-10. If I delete post #2, then my posts will be 1,3,4,5,6,7,8,9,10. If I create ten posts and delete them all, then the next post :id would be [1,3..10,21] but I would only have 11 posts.
I want to show the post number that's in the application and put it in the view against a total number of posts. So if you were looking at post #3, it might have an :id of 3, but it is post #2 in the database.
Here's what I tried so far:
posts_controller.rb
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.all.count.to_i
#posts_array = Post.pluck(:id).to_a
...
end
views/posts/show.html.erb
<%= #post.id %> of <%= #total_posts %> /
models/post.rb
def next
Post.where("id > ?", id).order(id: :asc).limit(1).first
end
def prev
Post.where("id < ?", id).order(id: :desc).limit(1).first
end
However, showing the :id of a resource is a security issue so I don't know how to do it better.
How can I make it so the show.html.erb view only shows the current index order of the total amount of resources as compared to the post_id?
An efficient way to do this could be
# app/controllers/posts_controller.rb
def show
#post = Post.friendly.find(params[:id])
#total_posts = Post.count
#post_index = Post.where("id <= ?", #post.id).count
end
# app/views/posts/show.html.erb
. . .
<%= #post_index %> of <%= #total_posts %>
. . .
You should avoid loading all posts (or even their id) if you can. This will become more and more expensive as the number of posts grows and will eventually become a bad bottleneck for performance.
If you're trying to find the 'array index' of a record (so to speak) you can do this:
Agency.order(id: :asc).offset(params[:index]).limit(1)
You don't really want to do any other way because then it will load EVERY record into rails which will be very slow. It's better to ask the database for only a single record (which is what 'offset' does). Just replace params[:index] with whatever the name of the params is, whether its params[:id], etc.
I did just want to address one thing you said:
However, showing the :id of a resource is a security issue so I don't know how to do it better
That's not a security issue. The app should be designed in a way where the ID of a resource is not special or "secret." If you have an ID of a record, your controller should work such that it "authorizes" certain actions and won't let you do something you're not supposed to (like a user deleting a post).
If you REALLY need to do this, then just hide the ID and use a slug instead, like example.com/this-is-a-post-slug. This can be done quite easily
Edit To answer your specific question...
ids = Agency.order(id: :asc).pluck(:id)
#post_index = ids.find_index(#post.id)
#next_post = ids[#post_index + 1]
#prev_post = ids[#post_index - 1]
You can now use #post_index in your view.
Note: #prev_post and #next_post will be nil when the page doesn't exist (i.e. the "next post" when you're on the last page), so you will need to check that.
Just try it:
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.count # this will return integer type data
#posts_array = Post.pluck(:id) # you don't need to_a as .pluck returns array
...
For the next part you could write:
def next
self.class.where("id > ?", id).limit(1).first # this use of id is secured.
end
def prev
self.class.where("id < ?", id).order(id: :desc).limit(1).first
end

Ruby on Rails 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

How to delete a single instance from a collection

I'm trying to delete a single instance from a database query. "l.remove" represents what i want to do but i know its wrong. I have tried delete and destroy. destroy didn't work and delete actually removed the data from the database. I just want the data removed from the variable. Can anyone help me?
<%
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.each do |l|
#temp = FlaggedCandidate.where(:flagged_user_id=>#owner.user_id, :list_id=>l.list_id)
if !#temp.nil?
l.remove
end
end
#candidate = FlaggedCandidate.new
%>
based on the code i assume that User has many ShoppingList.
You can do something like:
#job_list = #owner.shopping_lists.where( list_id: FlaggedCandidate.where( flagged_user_id: #owner.user_id ).pluck(:list_id) )
That could save the trouble of looping around.
You are trying to remove record from db. In order to just modify collection #job_list you need reject some unsatisfied elements. You can do it with select method (to select job_lists that flagged), or reject in opposite. This is how you code should looks like:
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.select! do |job_list|
FlaggedCandidate.where(
:flagged_user_id => #owner.user_id,
:list_id => job_list.list_id
).any?
end
#candidate = FlaggedCandidate.new
select! simply change the original collection, instead of doing #job_list = #job_list.select { ... }

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 store the result of my algorithm?

I have an algorithm that searches through all of my sites users, finding those which share a common property with the user using the algorithm (by going to a certain page). It can find multiple users, each can have multiple shared properties. The algorithm works fine, in terms of finding the matches, but I'm having trouble working out how to store the data so that later I'll be able to use each unit of information. I need to be able to access both the found users, and each of the respective shared properties, so I can't just build a string. This is an example of the output, being run from the perspective of user 1:
user 4
sharedproperty3
sharedproperty6
user 6
sharedproperty6
sharedproperty10
shareproperty11
What do I need to do to be able to store this data, and have access to any bit of it for further manipulation? I was thinking of a hash of a hash, but I can't really wrap my head around it. I'm pretty new to programming, and Ruby in particular. Thanks for reading!
EDIT - Here's the code. I'm fully expecting this to be the most incorrect way to do this, but it's my first try so be gentle :)
So if I'm understanding you guys correctly, instead of adding the interests to a string, I should be creating an array or a hash, adding each interest as I find it, then storing each of these in an array or hash? Thanks so much for the help.
def getMatchedUsers
matched_user_html = nil
combined_properties = nil
online_user_list = User.logged_in.all
shared_interest = false
online_user_list.each do |n| # for every online user
combined_properties = nil
if n.email != current_user.email # that is not the current user
current_user.properties.each do |o| # go through all of the current users properties
n.properties.each do |p| # go through the online users properties
if p.interestname.eql?(o.interestname) # if the online users property matches the current user
shared_interest = true
if combined_properties == nil
combined_properties = o.interestname
else
combined_properties = combined_properties + ", " + o.interestname
end
end
end
if shared_interest == true
matched_user_html = n.actualname + ": " + combined_properties
end
end
end
end
return matched_user_html
render :nothing => true
end
This returns an array of hashes with all users and their corresponding sharedproperties.
class User
def find_matching_users
returning Array.new do |matching_users|
self.logged_in.each do |other_user|
next if current_user == other_user # jump if current_user
# see http://ruby-doc.org/core/classes/Array.html#M002212 for more details on the & opreator
unless (common_properties = current_user.properties & other_user.properties).empty?
matching_users << { :user => other_user, :common_properties => common_properties }
end
end
end
end
end
In your view you can do something like this:
<%- current_user.find_matching_users.each do |matching_user| -%>
<%-# you can acccess the user with matching_user[:user] -%>
<%-# you can acccess the common properties with matching_user[:common_properties] -%>
<%- end -%>
You can use a hash table with the key being the user object and the value being an array of the shared properties . This is assuming that you first need to do a lookup based on the user .
Something like this :
#user_results = { user1 => [sharedproperty3,sharedproperty7] , user2 => [sharedproperty10,sharedproperty11,sharedproperty12]}
You can then acces the values like :
#user_results[user1]
or you can also iterate over all the keys using #user_results.keys

Resources