Ruby on Rails 4 fields_for number of repetitions - ruby-on-rails

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

Related

redirecting back to different object

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.

How can I change the number of items that end up in a list

I'm hacking away at a rails project and I wanted to modify the number of items that end up on a particular page. The page gets populated via an array of items.
For the life of me I can't figure out how to make it show only 2 instead of 4 items.
In the haml file there is this section:
%ul.story-list
- #stories.each do |story|
%li
- unless story.image.blank?
.img-container{ class: ((story.video.blank?)? "": "video-container") }
= image_tag(story.image_url, alt: story.name, class: ((story.video.blank?)? "": "js-has-video"), :video => story.video)
.story-data
%h4= story.name
%h5.location= story.location
%p.quote= story.story
- if story.get_connected?
= link_to 'Get Connected', connect_path
- elsif story.gather_supplies?
= link_to 'Gather Supplies', supplies_path
- elsif story.make_a_plan?
= link_to 'Make a plan', plan_path
The page shows up (on the server) with four story items, I want it to only show two. I was expecting to open the haml file and just delete some lines (or comment them out). I'm so confused.
So, I suspect the number of stories comes from a controller or something like that. ..but maybe it is coming from the placeholder data on the server?
In case you are inspired to help me, all the code is here
https://github.com/city72/city-72
The exact page I'm trying to modify is this one, I want it to only have two stories:
http://collier72.herokuapp.com/stories
Weirdly, in my local environment I can't edit the stories at all. That's what makes me thing the number of items comes from the data.
The stories controller is this tiny little file that doesn't specify the number of stories:
class StoriesController < ApplicationController
after_filter :static_content
def index
all_stories = EmergencyStory.order("index,id ASC").all
#selected_story = all_stories.select {|s| s.selected}.first
#stories = all_stories.collect.select {|s| !s.selected}
end
end
Open up this file:
https://github.com/city72/city-72/blob/master/app/controllers/stories_controller.rb#L8
Change that line from this:
#stories = all_stories.collect.select {|s| !s.selected}
to this:
#stories = all_stories.collect.select{|s| !s.selected}.slice(0,2)
From what I can tell, the fact it is returning 4 isn't intentional, it's just what is in the database. The slice(0,2) will return the first two items.
First, you have 3 stories that you are looking for, not 2. You have your #selected_story and then the remaining #stories. Second, you are retrieving ALL of the stories which will not scale when you get many stories in the database, so rendering this page will slow down over time. So you need to limit the number of records being returned by the database.
Get the selected story.
Then get the two next stories.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.where(selected: true).first
#stories = EmergencyStory.where(selected: false) # don't get selected
.limit(2) # limit records returned
.order("index,id ASC")
.all
end
end
If you were to further refine this you should put those two queries into methods into EmergencyStory.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.selected_story
#stories = EmergencyStory.recent_stories
end
end
class EmergencyStory < ActiveRecord::Base
def self.selected_story
where(selected: true).first
end
def self.recent_stories
where(selected: false).limit(2).order('index,id ASC').all
end
end

rails 3 searching and ordering by two fields

I have a list of questions with different positions and question group ids
how would i find all the questions with one id and pick out the one with the biggest position number.
is this close
<%= #question = Question.maximum('position', :conditions => {'question_group_id' => question_group_id'}) %>
using any of the answers i get
#<Question:0x3fe85c0>
how do i turn that into something we can read
First of I see an erb tag (<% %>) and the operation you perform will be better on your controller.
Now the query (in the controller) should be
#question = Question.where(question_group_id: question_group_id).
order("position desc").
limit(1). #because you only need one record!
first
You can do that with the following query.This will defiantly work for you.
#question = Question.where('question_group_id = ?',question_group_id).order("position desc").first
#question = Question.where(:question_group_id => question_group_id).order("position desc").first
First of all, I would put the query on the Model as a named scope and not in the Controller.
Given that, on your Question model:
scope :highest_position_by_question_group_id, lambda { |question_group_id|
where(question_group_id: question_group_id).order('position DESC').first
}
And on your Controller:
#question = Question.highest_position_by_question_group_id(question_group_id)

Ruby: Formatting table data to JSON

I'm trying to make a timeline for an the bugs and updates for an open source project. I'm new to ruby, but I'm getting some experience gradually.
I've created a table called historical_gems, with the following code in the model:
class HistoricalGem < ActiveRecord::Base
attr_accessible :build_date, :version
belongs_to :ruby_gem, :foreign_key => :gem_id
end
I'm using a JS Plugin (http://almende.github.com/chap-links-library/js/timeline/doc) that requires objects with two field names ('start' for the date and 'content' for the title) in the JSON Array to display the timeline using JS.
I believe I have to do something like this in the controller which defines my timeline method to render the JSON:
def timelinem
#name = params[:id]
#rpm = AbcTable.find_by_name(#name)
respond_to do |format|
format.json { render :json => #rpm.json_timelines }
end
end
Then I probably would have to define a 'json_timelines' method inside my model, maybe something like:
def json_timelines(gems = [])
dates = []
gem_id.each { |p|
gems << p
dates << p.build_date(gems)
end
}
end
I'm only starting out with RoR, and even after hours with guides and tutorials and debugging, I'm not able to put together this code. Can anyone help me out, please? I don't think I'm doing it right.
btw, don't be too harsh if I overlooked something obvious, I'm only 16 :)
The render :json => ... in your code should work fine (but with HistoricalGem instead of AbcTable) as long as json_timelines returns an object that's serializable as JSON (e.g., an Array or a Hash).
Try something like this for your method definition:
def json_timelines(gems = [])
gems.map do |g|
{
:content => g.title,
:date => g.build_date
}
end
end
The above snippet assumes your "historical_gems" table has "title" and "build_date" columns; if not, adjust the code to reflect the fields you actually want represented in your JSON.

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'.

Resources