Best Practice for Helper/Partial that render a Model driven select - ruby-on-rails

Let's assume I have a model Product, and I want a drop-down select box that contains all products. This drop-down is used in several views, so it is going to be created by a helper method. Where is the 'best practice' location to get the select options from Product? Do I set #products = Product.all in every controller action that needs to show the drop-down, or do I make the helper method self contained by having it call Product.all? Does the answer change if I am dealing with a partial, or if I am filtering the products (i.e. Product.in_category(#category))? MVC says use the controller, but DRY says use the helper.

Look at the collection_select form helper that's built in. You can pass in different collections (Product.all, Product.) as and where needed in different views.
collection_select
From the link:
collection_select(object, method, collection, value_method,
text_method, options = {}, html_options = {})
Returns and tags for the collection of existing
return values of method for object‘s class. The value returned from
calling method on the instance object will be selected. If calling
method returns nil, no selection is made without including :prompt or
:include_blank in the options hash.
The :value_method and :text_method parameters are methods to be called
on each member of collection. The return values are used as the value
attribute and contents of each tag, respectively. They can
also be any object that responds to call, such as a proc, that will be
called for each member of the collection to retrieve the value/text.
Example object structure for use with this method:
class Post < ActiveRecord::Base belongs_to :author end
class Author < ActiveRecord::Base has_many :posts def
name_with_initial
"#{first_name.first}. #{last_name}" end end
Sample usage (selecting the associated Author for an instance of Post,
#post):
collection_select(:post, :author_id, Author.all, :id,
:name_with_initial, prompt: true)
If #post.author_id is already 1, this would return:
Please
select D. Heinemeier
Hansson D. Thomas M. Clark

In my opinion, Controller should decide what data the user sees. How the user sees it can be decided by the view or by the helper.
So i would advise you to put
#products = Product.all
or
Product.in_category(#category)
in your controller
Any kind of filter you apply should be done in the controller as well

With rails being a model-view-controller (MVC) framework, you're going to want that logic to be on the model. Having some method that returns the options for your select would probably be best (though, take that with a grain of salt because these things change a lot with application). Something I might try would be along the lines of:
class Product < ActiveRecord::Base
def self.get_select_options(category=nil)
if category.nil?
Product.all
else
Product.in_category(category)
end
end
end
... which you could then call with Product.get_select_options or Product.get_select_options(#category)

Related

Use grouped_collection_select to group a model by enum

Say I have two classes
class Recording
belongs_to :conversation
end
class Conversation < ActiveRecord::Base
has_many :recordings
enum status: [ :active, :archived ]
end
And I want to select the conversation stored on the 'Recording' model. How can I use grouped_collection_select to group all of the records in the Conversation table by the two enums, active and archived in this case?
All of the examples I can find about grouped_collection_select refer to calling method on members to provide a collection; versus grouping an existing collection.
The grouped_collection_select method is not the best tool for your needs because it really deals with associated records of a main record, whereas you, if I understand correctly, simply want to add all Conversation records to a select tag, but grouped by its attribute, not association.
You can easily construct the grouped options manually, though. I'd put this code into a helper not to clutter the view template too much:
# app/helpers/conversations_helper.rb
module ConversationsHelper
def grouped_conversations_options_for_select(selected_conversation_id = nil)
options = {}
Conversation.statuses.keys.each do |status|
options[status] = Conversation.with_status(status).pluck(:name, :id)
end
grouped_options_for_select(options, selected_conversation_id)
end
end
# app/view/show.html.erb
<%= select_tag :conversation_id, grouped_conversations_options_for_select(params[:conversation_id]) %>
The helper first constructs a hash with the following structure:
{
active: [[ conversation.id, conversation.name ], [...]],
archived: [[ conversation.id, conversation.name ], [...]],
}
This hash can be then passed to the grouped_options_for_select which converts it to the <OPTIONS> tags including proper <OPTGROUP> tags. The helper also supports setting the currently selected value in the select options. Its output is then passed to the select tag in the view template.
For the helper to work, you also need to add the following scope to the Conversation model:
# app/models/conversation.rb
scope :with_status, ->(status) { where(status: statuses[status]) }

pass query parameter to render in rails

In an application a developer written code as
show.html.slim
= render #driver.trips.order("id DESC"), show_driver: false
The application dont have any partials like _driver.html.erb in the same resource.
In that page it shows all trips of a driver except driver name attribute.
another code snippet
= render Trip.order("id DESC"), show_driver: true
Model association is
driver has_many :trips
trip belongs_to :driver
Is it possible to pass query as a param to render? and which route it states?
You can pass whatever you want to render, as long as it defines to_partial_path or is a collection of such objects. ActiveRecord defines it (this is primarily how it is used).
You mentioned that there is no _driver partial, however, what you should be looking for is trips/_trip.

Set Position Value from Index for nested model attributes

How can I set a position field in the attributes for a nested model so that my has_many relationship remembers it's sort order?
I want to set the position from the index so that it reflects the order the relations have been dragged into.
I have a form with Nested fields, using the cocoon gem with JQuery Sortable allowing each field-set to be drag-sortable.
I want to update the order of all the fields on saving the form.
Try to use "act_as_list" gem.
class TodoList < ActiveRecord::Base
has_many :todo_items, -> { order(position: :asc) }
end
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
acts_as_list scope: :todo_list
end
todo_list = TodoList.find(...)
todo_list.todo_items.first.move_to_bottom
todo_list.todo_items.last.move_higher
Refer: https://github.com/swanandp/acts_as_list
If you have the standard CRUD/restful routes and controller actions set up, then all you are wanting to do is to call "update_attributes(:position => 3)" on an instance of the nested class (education in this case).
The usual route to update an education which is nested under "resume" would be
UPDATE /resumes/123/educations/456
so, you'll be making an ajax call to this url. The "UPDATE" method isn't really an update method, it's sort of spoofed by passing through a parameter "_method" = "UPDATE", so your ajax call will need to include this parameter too.
So, basically, on the "finished dragging" event, you're going to be making an ajax call to this url
"/resumes/<%= #resume.id %>/educations/<%= education.id %>"
and passing data like
{"_method": "UPDATE", "education[position]": <new position>}
This should update the position of the education object.
The other remaining issue is that, with acts_as_list, when we change the position of one item, we want the others to adjust their position automatically, too. I'm not sure if acts_as_list does this automatically. Try it.
Ok, non ajaxy version.
Let's assume that your form is a form_for #resume, which means that it will call the resumes#create or resumes#update action.
If all the education rows in your list have a hidden field like this
<%= hidden_field_tag "resume[education_ids][]", education.id %>
then when the form is submitted, they will go through into an array in params[:resume][:education_ids] in the order in which they appear in the page when the form was submitted, which is what you want.
The association gives you the setter method Resume#education_ids, allowing you to set the associations, in order, this way.
Ie, your update action will (if it's a normal update action) be saying something like
#resume = Resume.find_by_id(params[:id])
if #resume.update_attributes(params[:resume])
...
in this case, this will be saying
#resume.update_attributes(:education_ids => [5,6,2,1])
which is like saying "set my educations to be those with ids 5,6,2,1, in that order.
CAVEAT: in my version of rails (this might be fixed in subsequent version), if you use this _ids method, and it already has associations, but in a different order, it WILL NOT reorder them. Give it a go and see what happens.
This can't be the best way to do it, but I have got this working in my controller.
p = 1
experiences = []
params[:user][:resume_attributes][:experiences_attributes].each do |e|
e = e.last.merge(:position=>p)
experiences << e
p = p + 1
end
params[:user][:resume_attributes][:experiences_attributes] = experiences
which at least illustrates what i want to achieve.

How to pass attribute of one model as a param to a different model?

I have a Favorite model and an Article model. The Favorite has an attribute fav_id that is equal to the the id of Article.
I want to create a link_to #article.title and pass a param of :id=>#favorite.fav_id.
favorites controller code:
def show
#favorite = Favorite.find(params[:id])
#article = Article.find(params[:id])
end
view/favorite/show code:
<%= link_to #article.title, article_path(:controller=>:article, :id=>#favorite.fav_id, :action=>'view')
When I load the page, the favorite id is used, which is fine. I just want to pass favorite.fav_id to the #article = Article.find(params[:id]). That way, the link will show the article title instead of a number (fav_id). In the future id like to be able to show #article.description and other attributes too.
I have also considered passing the article attributes to favorite, but this seems like itd be a heavier load on the database so I've so far avoided that
I've also tried a :through => association. Maybe I did it wrong, but :through didn't work when I tried it. Any suggestions?
Rails 4.2.0
Ruby 2.1.5
If your Favorite model has an attribute, pointing to another model (Article), that means, by definition, that there's an association between those models. So you should explicitely set that association in your models.
First of all you need to define the association type. In your case it an be either a has_one association in case an article may correnpond to only one favorite or a has_many association if one article may correspond to multiple favorits (multiple favourities can have the save fav_id).
Then you need to set up your association in both the Article and the Favorite models.
In your app/models/article.rb it will go like this:
has_one :favorite, foreign_key: :fav_id
In your app/models/favorite.rb it will go like this:
belongs_to :article, foreign_key: :fav_id
I recommend you though to simplify that by changing the field name from fav_id to article_id so that you can drop the foreign_key parameter in both definitions.
Make sure to read the API docs for more information on working with associations.
After you set up that association, you can easily use it in the controller.
def show
#favorite = Favorite.find params[:id]
#article = #favorite.article
end
And then just use link_to #article.title, #article in your view.
I cant clearly understand what you are going to get, but this:
`
def show
#favorite = Favorite.find(params[:id])
#article = Article.find(params[:id])
end
Is completely wrong. You are trying to use same :id parameter to find either Article and Favorite. I don`t think this is behavior you are waiting for.
The Favorite has an attribute of .fav_id
Also, why Favorite model has fav_id attribute? Is it Single Table Inheritance? Or what? Rails way would be, for example, Favorite to have article_id.

Rails: small controlles, implementing all logic inside models

I wrote this code in controller:
def list
#codes = Code.order("created_at")
#languages = Language.order('name').collect {|l| [l.name, l.coderay]}
#codes is an array of posts. Each code has language field for cpp or text string. It contains coderay token.
#languages is array of programming languages in format ['C++', 'cpp'], ['Plain Text', 'text'].
In other words, format of Language is :name, :coderay. I use it only in view to make select box.
So I use :coderay as primary key, but ruby added own PK :id to this model. And these models are not linked.
IDE writes me this warning:
Controller action should call one model method other than an initial
find or new
This inspection warns if a controller
action contains more than one model method call, after the initial
.find or .new. It’s recommended that you implement all business logic
inside the model class, and use a single method to access it
What is a best solution to solve this problem?
1) Add 1-to-m link between Codes and Language and make :coderay PK.
2) Ignore this warning
3) Move Language.order('name').collect {|l| [l.name, l.coderay]} to view.
I think the best solution is (1), how can I do this?
3 is the best in this case if u need it only for one select
select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
example from rails documentation
And don't create association that u don't need for your business logic.
You can implement option 1 to add a 1-to-1 link between your Language and Code by using a belongs to, and setting the foreign key.
code.rb
belongs_to :language, foreign_key: 'coderay', primary_key: 'language'
language.rb
has_many :codes, foreign_key: 'coderay', primary_key: 'language'
However if you want to load static data for a select box, I usually prefer to do that from a before_filter in the controller and pass it across to the view.
You might also like the decent_exposure gem. - https://github.com/voxdolo/decent_exposure

Resources