Rails 4 - How to populate an array through a form - ruby-on-rails

I have three tables: illness, symptom and a secondary table to link the other two on a has_many through: relationship.
I wish to implement a system where users type in their symptoms and receive a list of possible illnesses.
Using the code below I can use find(1,2), for example, to show all illnesses that have symptoms 1 and 2 simultaneously. However, I want these two constants to be user-supplied data fed through a form. I'm somewhat new to RoR so I don't know how to do this.
symptoms = Symptom.find(params[:symptom_ids]) # symptom_ids is an array of ids
illnesses = Illness.joins(illness_symptoms: :symptom)
.where("symptoms.id in (?)", symptoms)
.group("illnesses.id")
.having("count(illnesses.id) >= ?", symptoms.length)
In the model:
scope :with_symptoms, -> (symptoms) { joins(illness_symptoms: :symptom)
.where("symptoms.id in (?)", symptoms)
.group("illnesses.id")
.having("count(illnesses.id) >= ?", symptoms.length) }
I use these with Illness.with_symptoms(Symptom.find(1,2)) (I'm looking for a way to replace 1,2 with user-supplied data).

You can do something like this in a form:
<% form_tag some_path do %>
<% #symptoms.each do |symptom| %>
<%= label_tag do %>
<%= check_box_tag "symptom_ids[]", symptom.id %>
<%= symptom.name %>
<% end %>
<% end %>
<% end %>
User will be able to use checkboxes to choose symptoms.

Related

Rails 5.2 each statement without duplicates values

<% Schedule.all.each do |schedule| %>
<%= link_to schedule.county, events_path(County: schedule.county) %>
Displays duplicates in the Schedule table
I've tried all below without luck in displaying no duplicates.
<% Schedule.all.each.uniq do |schedule| %>
<% Schedule.distinct.pluck(:county) do |schedule| %>
<% Schedule.distinct.each.pluck(:county) do |schedule| %>
They display nothing for some reason.
Edit:
Associations --
Event has_many :schedules, inverse_of: :event
Schedule belongs_to :event
So the original each block does bring in what I want but grabs the county for each schedule in the database. This results in duplicate county listings. I'm trying to remove these duplicates which I presumed was simple enough by adding a uniq method to the statement but looks like it requires joins. Thanks.
It depends on the association between schedule and country model. Assuming Schedule belongs to country
<% Schedule.joins(country).select(:country).distinct.each do |schedule| %>
<%= link_to schedule.county, events_path(County: schedule.county) %>
<% end %>
<% Schedule.select(:county).distinct.each do |schedule| %>
So looks like I didn't need a join. It was more just a select and then use distinct method which works. Thanks #Tacyons for putting me in the right direction.

ActiveRecord_Associations_CollectionProxy:0x0000000e490b98 in RoR

I'm creating a application using ruby on rails, but currently i'm suffering a problem like db relation, below my code:
Company
has_many :posts, :foreign_key => :company_id
Post
belongs_to :companies, :foreign_key => :company_id
controller
#post = current_user.companies.all
view
<% #post.each do |p| %>
<%= p.posts.post_title %>
<% end %>
Showing error above code.
If I debug like use <%= debug p.posts %> then showing all posts, which is under my companies but when I use <%= debug p.posts.post_title %> then showing ActiveRecord_Associations_CollectionProxy:0x0000000e490b98
Thanks
I think the problem here is that you are trying to call the method :post_title on p.posts, which is an ActiveRecord::Associations::CollectionProxy object.
In your example, p is a Company object, which has a method posts, which returns to you a CollectionProxy object that acts a lot like a list of posts. That list will not have a method post_title, but each element of that list will have a method post_title
So, instead of
<% #post.each do |p| %>
<%= p.posts.post_title %>
<% end %>
You will want something like:
<% #post.each do |company| %>
<% company.posts.each do |post| %>
<%= post.post_title %>
<% end %>
<% end %>
Two additional things to note:
1) The variable #post is inaccurately named. Inaccurate variable names will lead to confusion when trying to understand what is happening. current_user.companies.all returns a list of companies, and therefore, it should read:
#companies = current_user.companies.all
not
#post = current_user.companies.all
2) The actual error that is being shown to you likely says something like
Undefined Method 'post_title' for ActiveRecord_Associations_CollectionProxy:0x0000000e490b98
Not just
ActiveRecord_Associations_CollectionProxy:0x0000000e490b98
When debugging and asking for help, it's very important to note the entire message of the exception being raised.
Because companiy has_many :posts........ posts are objects you need a loop to show all posts e.g
p.posts.each do |post|

Nested comments from scratch

Let's say I have a comment model:
class Comment < ActiveRecord::Base
has_many :replies, class: "Comment", foreign_key: "reply_id"
end
I can show a comment instance`s replies in a view like so:
comment.replies do |reply|
reply.content
end
However, how do I loop through the replies of the reply? And its reply? And its reply ad infitum? I'm feeling we need to make a multidimensional array of the replies via class method and then loop through this array in the view.
I don't want to use a gem, I want to learn
It seems like what you have is one short step away from what you want. You just need to use recursion to call the same code for each reply as you're calling for the original comments. E.g.
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
NB: this isn't the most efficient way of doing things. Each time you call comment.replies active record will run another database query. There's definitely room for improvement but that's the basic idea anyway.
Would using a nested set still count as 'from scratch'?
The short description of a nested set is a database-specific strategy of querying hierarchies by storing/querying pre- and post-order tree traversal counts.
A picture is worth a thousand words (see also, the wikipedia page on nested sets).
There are a bunch of nested set gems, and I can personally speak for the quality of Awesome Nested Set and Ancestry
Then, Awesome Nested Set (I know from experience, presumably Ancestry too) provide helpers to do a single query to pull up all records under a tree, and iterate through the tree in sorted depth-first order, passing in the level while you go.
The view code for Awesome Nested Set would be something like:
<% Comment.each_with_level(#post.comments.self_and_descendants) do |comment, level| %>
<div style="margin-left: <%= level * 50 %>px">
<%= comment.body %>
<%# etc %>
</div>
<% end %>
I just made that up from vague memories, and it's been a while, so this is where it can be "an exercise for the reader"
My approach is to make this done as efficient as possible.
First lets address how to do that:
DRY solution.
Least Number of queries to retrieve the comments.
Thinking about that, I have found that most of the people address the first but not the second.So lets start with the easy one.
we have to have partial for the comments so referencing the answer of jeanaux
we can use his approach to display the comments and will update it later in the answer
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
We must now retrieve those comments in one query if possible so we can just do this in the controller. to be able to do this all comments and replies should have a commentable_id (and type if polymorphic) so that when we query we can get all comments then group them the way we want.
So if we have a post for example and we want to get all its comments we will say in the controller.
#comments = #post.comments.group_by {|c| c.reply_id}
by this we have comments in one query processed to be displayed directly
Now we can do this to display them instead of what we previously did
All the comments that are not replies are now in the #comments[nil] as they had no reply_id
(NB: I don like the #comments[nil] if anyone has any other suggestion please comment or edit)
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: #comments[nil] %>
</div>
All the replies for each comment will be in the has under the parent comment id
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: #comments[comment.id] %>
</div>
To wrap up:
We added an object_id in the comment model to be able to retrieve
them( if not already there)
We added grouping by reply_id to
retrieve the comments with one query and process them for the view.
We added a partial that recursively displays the comments (as
proposed by jeanaux).
It seems like you need a self-referential association. Check out the following railscast: http://railscasts.com/episodes/163-self-referential-association
We've done this:
We used the ancestry gem to create a hierarchy-centric dataset, and then outputted with a partial outputting an ordered list:
#app/views/categories/index.html.erb
<% # collection = ancestry object %>
<%= render partial: "category", locals: { collection: collection } %>
#app/views/categories/_category.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
<%= link_to "+", admin_category_new_path(category), title: "New Categorgy", data: {placement: "bottom"} %>
<% if category.prime? %>
<%= link_to "", admin_category_path(category), title: "Delete", data: {placement: "bottom", confirm: "Really?"}, method: :delete, class: "icon ion-ios7-close-outline" %>
<% end %>
<!-- Page -->
<%= link_to "", new_admin_category_page_path(category), title: "New Page", data: {placement: "bottom"}, class: "icon ion-compose" %>
</div>
<!-- Pages -->
<%= render partial: "pages", locals: { id: category.name } %>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
We also made a nested dropdown:
#app/helpers/application_helper.rb
def nested_dropdown(items)
result = []
items.map do |item, sub_items|
result << [('- ' * item.depth) + item.name, item.id]
result += nested_dropdown(sub_items) unless sub_items.blank?
end
result
end
That can be solved with resursion or with a special data structure. Recursion is simpler to implement, whereas a datastructure like the one used by the nested_set gem is more performant.
Recursion
First an example how it works in pure Ruby.
class Comment < Struct.new(:content, :replies);
def print_nested(level = 0)
puts "#{' ' * level}#{content}" # handle current comment
if replies
replies.each do |reply|
# here is the list of all nested replies generated, do not care
# about how deep the subtree is, cause recursion...
reply.print_nested(level + 1)
end
end
end
end
Example
comments = [ Comment.new(:c_1, [ Comment.new(:c_1a) ]),
Comment.new(:c_2, [ Comment.new(:c_2a),
Comment.new(:c_2b, [ Comment.new(:c_2bi),
Comment.new(:c_2bii) ]),
Comment.new(:c_2c) ]),
Comment.new(:c_3),
Comment.new(:c_4) ]
comments.each(&:print_nested)
# Output
#
# c_1
# c_1a
# c_2
# c_2a
# c_2b
# c_2bi
# c_2bii
# c_2c
# c_3
# c_4
And now with recursive calls of Rails view partials:
# in your comment show view
<%= render :partial => 'nested_comment', :collection => #comment.replies %>
# recursion in a comments/_nested_comment.html.erb partial
<%= nested_comment.content %>
<%= render :partial => 'nested_comment', :collection => nested_comment.replies %>
Nested Set
Setup your database structure, see the docs: http://rubydoc.info/gems/nested_set/1.7.1/frames That add the something like following (untested) to your app.
# in model
acts_as_nested_set
# in controller
def index
#comment = Comment.root # `root` is provided by the gem
end
# in helper
module NestedSetHelper
def root_node(node, &block)
content_tag(:li, :id => "node_#{node.id}") do
node_tag(node) +
with_output_buffer(&block)
end
end
def render_tree(hash, options = {}, &block)
if hash.present?
content_tag :ul, options do
hash.each do |node, child|
block.call node, render_tree(child, &block)
end
end
end
end
def node_tag(node)
content_tag(:div, node.content)
end
end
# in index view
<ul>
<%= render 'tree', :root => #comment %>
</ul>
# in _tree view
<%= root_node(root) do %>
<%= render_tree root.descendants.arrange do |node, child| %>
<%= content_tag :li, :id => "node_#{node.id}" do %>
<%= node_tag(node) %>
<%= child %>
<% end %>
<% end %>
<% end %>
This code is from an old Rails 3.0 app, slightly change and untested. Therefore it will probably not work out of the box, but should illustrate the idea.
This will be my approach:
I have a Comment Model and a Reply model.
Comment has_many association with Reply
Reply has belongs_to association with Comment
Reply has self referential HABTM
class Reply < ActiveRecord::Base
belongs_to :comment
has_and_belongs_to_many :sub_replies,
class_name: 'Reply',
join_table: :replies_sub_replies,
foreign_key: :reply_id,
association_foreign_key: :sub_reply_id
def all_replies(reply = self,all_replies = [])
sub_replies = reply.sub_replies
all_replies << sub_replies
return if sub_replies.count == 0
sub_replies.each do |sr|
if sr.sub_replies.count > 0
all_replies(sr,all_replies)
end
end
return all_replies
end
end
Now to get a reply from a comment etc:
Getting all replies from a comment: #comment.replies
Getting the Comment from any reply: #reply.comment
Getting the intermediate level of replies from a reply: #reply.sub_replies
Getting all levels of replies from a reply: #reply.all_replies
I've had various generally bad experience with the different hierarchy gems available for ActiveRecord. Typically you do not want to do this yourself as your queries will end up being very inefficient.
The Ancestry gem was ok, but I had to move away from it because 'children' is a scope and NOT an association. This means you CANNOT use nested attributes with it because nested attributes only work with associations, not scopes. That may or may not be a problem depending on what you are doing, such as ordering or updating siblings through the parent or updating entire subtrees/graphs in a single operation.
The most efficient ActiveRecord gem for this is the Closure Tree gem and I had good results with it, with the caveat that splatting/ mutating entire sub-trees was diabolical because of the way ActiveRecord works. If you don't need to compute things over a tree when doing updates then it is the way to go.
I've since moved away from ActiveRecord to Sequel and it has recursive common table expression (RCTE) support which is used by its built-in tree plugin. An RCTE tree is as fast as is theoretically possible to update (just modify a single parent_id as in a naive implementation) and querying is also typically orders of magnitude faster than other approaches because of the SQL RCTE feature it uses. It is also the most space efficient approach since there is just parent_id to maintain. I am not aware of any ActiveRecord solutions that support RCTE trees because ActiveRecord doesn't cover nearly as much of the SQL spectrum that Sequel does.
If you're not wedded to ActiveRecord then Sequel and Postgres is a formidable combination IMO. You will find out the deficiencies in AR when your queries become ever so slightly complex. There is always pain moving to another ORM as its not the out of the box stock rails approach but I have been able to express queries that I couldn't do with ActiveRecord or ARel (even though they were pretty simple), and generally improved query performance across the board 10-20 times over what I was getting with ActiveRecord. In my use case with maintaining trees of data its hundreds of times faster. That means tens to hundreds times less server infrastructure I need for the same load. Think about it.
You'd collect the reply's replies within each Reply iteration.
<% comment.replies do |reply| %>
<%= reply.content %>
<% reply_replies = Post.where("reply_id = #{reply.id}").all %>
<% reply_replies .each do |p| %>
<%= p.post %>
<% end
<% end %>
Though im not sure if it'd be the most conventional way cost-wise.

Rails3 help with joins scope

Have simple Order - Item app. No cart. Need help with joined scopes for my reports.
Item is polymorphic (belongs_to :itemable, :polymorphic => true) with Products, Services and Policies. Policies belongs to Vendor.
Goal is list out how much was sold by Vendor but still be able to use the scopes for Orders as seen below.
Scopes in my Orders model
scope :closed_today, lambda { where("closed_date IS NOT ? AND closed_date >= ? AND closed_date < ?", nil, Time.now.midnight, Time.now.midnight.tomorrow)}
scope :closed_yesterday, lambda { where("closed_date IS NOT ? AND closed_date >= ? AND closed_date < ?", nil, Time.now.midnight.yesterday, Time.now.midnight)}
...
Something like - Guess in Orders Model?
scope :with_policies, joins(:items).where(:itemable_type => "Policy")
And Final Result being able to group by vendor and get amounts sold.
<% for v in #orders.closed_today.with_policies.group("itemable.vendor_id") %>
<%= v.somehow_get_to.vendor.name %> Sold: <%= v.somehow_get_to.collect.sum(&:price) %>
Price is the item field that holds how much that item sold for.
I know you can't group as shown above, so any pointers on how to do that would be great. Would like to avoid adding vendor_id to my Items table.
UPDATE
Here is what I ended up doing.
Added vendor_id to items. Made my life easier. Sure this is a better way of doing this.
In my Items model
scope :in_orders_with, lambda { |orders, type| where('itemable_type IS ?', type).joins(:order) & orders }
In my view (since #orders.closed_today is actually passed in from another view.)
<% orders = #orders.closed_today %>
<p>
<% #items = Item.in_orders_with(orders, "Policy") %>
<% if !#items.blank? %>
<% #items.group_by{ |o| o.vendor }.each do |vendor, items| %>
<%= vendor.name %> <br />Qty Sold:<%= items.count %> Dollars Sold:<%= items.collect.sum(&:price) %><br /><br />
<% end %>
<% else %>
Nothing to report from Policies<br />
<% end %>
</p>
<% #orders.closed_today.with_policies.group_by{|o| o.itemable.vendor}.each do |vendor, orders| %>
<%= vendor.name %> SOLD: <%= orders.sum(&:price) %>
<% end %>

How can I display a list of three different Models sortable by the same :attribute in rails?

I have a Campaign model which has_many Calls, Emails, and Letters.
For now, these are each a separate Model with different controllers and actions (although I would like to start to think of ways to collapse them once the models and actions stabilize).
They do share two attributes at least: :days and :title
I would like a way to represent all the Calls, Emails, and Letters that belong_to a specific Campaign as a sortable collection (sortable by :days), in a way that outputs the model name and the path_to() for each.
For example (I know the below is not correct, but it represents the kind of output/format I've been trying to do:
#campaign_events.each do |campaign_event|
<%= campaign_event.model_name %>
<%= link_to campaign_event.title, #{model_name}_path(campaign_event) %>
end
Thanks so much. BTW, if this matters, I would then want to make the :days attribute editable_in_place.
Here is what I've got working, but want some additional insights
module CampaignsHelper
def campaign_events
return (#campaign.calls + #campaign.emails + #campaign.letters).sort{|a,b| a.days <=> b.days}
end
end
In the VIEW:
<% #campaign_events = campaign_events %>
<% #campaign_events.each do |campaign_event| %>
<% model_name = campaign_event.class.name.tableize.singularize %>
<p>
<%= link_to campaign_event.title, send("#{model_name}_path", campaign_event) %>
<%= campaign_event.days %>
</p>
<% end %>
Like this?
# controller
#campaign = Campaign.find(params[:id])
#campaign_events = (#campaign.calls + #campaign.emails + #campaign.letters).sort{|a,b| a.days <=> b.days}
# view
#campaign_events.each do |campaign_event|
<%= campaign_event.model_name %>
<%= link_to campaign_event.title, #{model_name}_path(campaign_event) %>
end
In controller you find all campaign events and sort it by days field

Resources