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
Related
I'd like to incorporate a step to check for an existing relation object as part of my model creation/form submission process. For example, say I have a Paper model that has_and_belongs_to_many :authors. On my "Create Paper" form, I'd like to have a authors_attributes field for :name, and then, in my create method, I'd like to first look up whether this author exists in the "database"; if so, then add that author to the paper's authors, if not, perform the normal authors_attributes steps of initializing a new author.
Basically, I'd like to do something like:
# override authors_attributes
def authors_attributes(attrs)
attrs.map!{ |attr| Author.where(attr).first_or_initialize.attributes }
super(attrs)
end
But this doesn't work for a number of reasons (it messes up Mongoid's definition of the method, and you can't include an id in the _attributes unless it's already registered with the model).
I know a preferred way of handling these types of situations is to use a "Form Object" (e.g., with Virtus). However, I'm somewhat opposed to this pattern because it requires duplicating field definitions and validations (at least as I understand it).
Is there a simple way to handle this kind of behavior? I feel like it must be a common situation, so I must be missing something...
The way I've approached this problem in the past is to allow existing records to be selected from some sort of pick list (either a search dialog for large reference tables or a select box for smaller ones). Included in the dialog or dropdown is a way to create a new reference instead of picking one of the existing items.
With that approach, you can detect whether the record already exists or needs to be created. It avoids the need for the first_or_initialize since the user's intent should be clear from what is submitted to the controller.
This approach struggles when users don't want to take the time to find what they want in the list though. If a validation error occurs, you can display something friendly for the user like, "Did you mean to pick [already existing record]?" That might help some as well.
If I have a model Paper:
class Paper
include Mongoid::Document
embeds_many :authors
accepts_nested_attributes_for :authors
field :title, type: String
end
And a model Author embedded in Paper:
class Author
include Mongoid::Document
embedded_in :paper, inverse_of: :authors
field :name, type: String
end
I can do this in the console:
> paper = Paper.create(title: "My Paper")
> paper.authors_attributes = [ {name: "Raviolicode"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Raviolicode">]
> paper.authors_attributes = [ {id: paper.authors.first, name: "Lucia"}, {name: "Kardeiz"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Lucia">, #<Author _id: 531cd95931302ea603010000, name: "Kardeiz">]
As you can see, I can update and add authors in the same authors_attributes hash.
For more information see Mongoid nested_attributes article
I followed the suggestion of the accepted answer for this question and implemented a reject_if guard on the accepts_nested_attributes_for statement like:
accepts_nested_attributes_for :authors, reject_if: :check_author
def check_author(attrs)
if existing = Author.where(label: attrs['label']).first
self.authors << existing
true
else
false
end
end
This still seems like a hack, but it works in Mongoid as well...
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)
I'm fairly new to RoR and having trouble wrapping my head around how to do this.
Basically I want to design a drop down menu that will dynamically populate a drop down of newspapers from the database. Once the paper is selected, I want the user to be able to select an issue category (ex: billing), then a specific issue_type (ex: credit card charge), then the contact type (email or phone) (total of 4 drop downs).
The issue category, issue_type, and contact_type all belong to Issuelog. Each Issuelog should belong to a specific newspaper, as per my model code. I want the user to be able to record the volume of each kind of contact for each type of issue for each paper, with a very standard set of selections available. The newspaper table will not change after the submission, it will simply create an Issuelog that will correlate to that particular paper (the id created by default - not sure if I need to create any additional keys in this scenario).
Issuelog
class Issuelog < ActiveRecord::Base
belongs_to :newspaper
attr_accessible :category, :contact_type, :issue_type
end
Newspaper
class Newspaper < ActiveRecord::Base
has_many :issuelogs
attr_accessible :affiliate_group, :name
end
I'm having trouble understanding how I will need to structure this overall to achieve what I want. Will I need to use JavaScript in my view, and does my model design make sense for what I'm trying to achieve?
In controller's action
#newspapers = Newspaper.find(:all)
In model there are many that you can use, You can use something like this.
<%= select("newspaper", "ids", #newspapers.collect {|p| [ p.name, p.id ] }, { :prompt => 'Select' }, :onChange => 'do_your_thing()') %>
I hope this helps, But tell if you need any clarification
So tasking.point_of_contact is an integer value that is the same number as the user.id whose name (name is defined as :firstname + :lastname) I want to display.
How can I take the value in tasking.point_of_contact and use it to call the name of the corresponding user.id?
Right now I have:
<td><%= tasking.point_of_contact %></td>
I want that to display the name of the user whose user.id equals tasking.point_of_contact.
The relationship between user and taskings is a many-to-many.
I hope I've give you enough information. I may have been redundant, but I just want to be clear.
If you haven't set up your relationship in your tasking model yet, you want to do that.
belongs_to :user, :class_name => "User", :foreign_key => "point_of_contact"
Then, where ever you're doing the initial query
#tasking = Tasking.where(whatever)
You'll want to eager load the users so you don't get a N+1 issue (you can google this, just an efficiency thing).
#tasking = Tasking.includes(:user).where(whatever you want to find)
Then in your view you'll do
tasking.user.name
There are some helper methods generated automatically when you have a has_and_belongs_to_many relationship. Specifically, the others generated method should be helpful. In your case, it would be tasking.users. However, I'm not sure your design is correct...
I am trying to get data for my collection_select list.
Details
There are 4 tables involved
volunteers
has_many :signed_posts
has_many :posts, :through=>:signed_posts
signed_posts
belongs_to :volunteer
belongs_to :post
posts
belongs_to :organization
has_many :signed_posts
has_many :volunteers, :through=>:signed_posts
organizations
has_many :post
If I would have to write in plain SQL, it would be like below.This sql is for postgreSQL. I would like to write in rails 3.2.1 syntax.
Select sp.id,p.title ||'-'|| o.name AS project from signed_posts sp
inner join volunteers v on v.id=sp.volunteer_id
inner join posts p on p.id=sp.post_id
inner join organizations o on o.id=p.organization_id
where v.id=1
As a result, I want to get signed_posts.id and posts.title - organizations.name for a volunteer id 1
which I would like to use on dropdown list.
Thank you very much for your help
My Solution
I solved the problem using hash table like this one
#signed_projects=Volunteer.find(1).signed_posts.joins(:post)
#ddl_test=Hash.new
#signed_projects.each do |signed_project|
ddl_test[signed_project.post.title+"-"+signed_project.post.organization.name]=signed_project.id
end
On View
<%=select_tag 'ddl_test',options_for_select(#ddl_test.to_a),:prompt => 'Please select the project'%>
I am not completely satisfied. I think there must be some elegant solution. If any body has better idea, please let me know
There are a couple ways to do this. In your controller you would want a statement such as:
#signed_posts = Volunteer.find(1).signed_posts.includes({:post => :organization})
After that, one way would be to create a method in your SignedPost model to return the data you need, something like the following
def post_name_with_organization
"#{id} #{post.title} - #{post.organization.name}"
end
Then, in your collection_select, you can use (this is partially guessing, since I don't know the specifics of your form):
form.collection_select(:signed_post_id, #signed_posts, :id, :post_name_with_organization)
Edit based on question updates
If you want to use select_tag and options_for_select as you did in your solution, a more elegant way would be to create this variable in your controller:
#signed_posts_options = #signed_posts.map {|sp| [sp.post_name_with_organization, sp.id]}
Then in your view:
<%=select_tag 'ddl_test',options_for_select(#signed_posts_options),:prompt => 'Please select the project'%>