Have written some RoR sites in the past, but never bothered too much at DRYing up my views as it's only ever been me looking at the code.
Just starting a new site, that is likely to be a collaboration, and need help
I call this line multiple times in my views, and from multiple places.
<%= Person.find_by_id(rider.person_id).name %>
I'd like to be able to just do
<%= get_name(rider.person_id) %>
So I'd assume I'd put this code somewhere
def get_name=(id)
Person.find_by_id(id).name
end
But where? I've tried in the model and as a helper, but always get nomethoderror.
You're wrong in naming this method.
Why you put "=" sign in the method name?
You should call this code only in controller, and in the views, only render result.
The best place for this method is a helper.
def get_person_name_by_id(id)
Person.find_by_id(id).name || nil
end
try application_controller.rb
How about a method on the Rider class that just returns the name?
def name
person = Person.find_by_id( #person_id )
if !person.nil?
return person.name
end
return nil
end
used as
<%= rider.name %>
or, you could use a static/class method on the Person class.
def Person.get_name( rider )
if !rider.nil?
person = find_by_id( rider.person_id )
if !person.nil?
return person.name
end
end
return nil
end
called as
<%= Person.get_name( rider ) %>
Note that I dropped the reference to the id in the view code.
Rails is all about convention over configuration. All of it's helper methods assume the most common options unless overridden. However ActiveRecord models don't have a useful default to_s.
Specifying the default action:
class Person < ActiveRecord::Base
def to_s
name
end
end
Now every time you try to evaluate a person instance in string context you'll get a name.
So
<%= Person.find_by_id(rider.person_id).name %>
Can now be replaced with
<%= rider.person %>
For anything else you can specify field and methods.
Related
Newbie to RoR. I can't grasp the concept of how to call a simple method on my form controller from my view. I want to collect 2 pieces of information form the view, call a method on the form controller that will retrieve a piece of information based on the parameters, and then display the piece of information on that same view or another one. Right now, I was trying to use a form controller instance variable to accomplish the displaying of the new piece of information--not sure how I will refresh the view to display it but that is a future hurdle. Right now, I can click my Submit button without getting any errors but it is clearly not hitting my form controller method.
Here is my erb file for the view:
<h1>Enter Required Information</h1>
<%= form_tag (get_hotel_recommendation_path) do %>
<%= label_tag(:name, "Name:") %>
<%= text_field_tag(:name) %>
<%= label_tag(:date, "Date (yyyy-mm-dd):") %>
<%= text_field_tag(:date) %>
<%= submit_tag("Submit") %><br><br>
<%= label_tag(:recommendation, "Recommendation:") %>
<%= #recommended_hotel_name %>
<% end %>
Here is my form controller code:
class RecommendHotelController < ApplicationController
#recommended_hotel_name = ''
def collect_info
end
def get_hotel_recommendation
#recommended_hotel_name = Member.recommended_hotel_name( params[:name], params[:date] )
end
end
I really just want a simple and easy way to do this--not necessarily the best. I just need a quick UI to demonstrate my model code. And I need to get it done soon.
New form controller code:
class RecommendHotelController < ApplicationController
def collect_info
end
def get_hotel_recommendation
redirect_to recommend_hotel_path
end
protected
helper_method :recommended_hotel_name
def recommended_hotel_name
unless (params[:name].nil?)
Member.recommended_hotel_name(params[:name], params[:date])
end
end
end
Once the controller hands over to the view, there's no going back to call additional methods. It is the controller's job to prepare everything the view might need in advance. Once inside the render phase, there's no way to call controller methods.
The exception to this is helper methods which can be called. You need to declare any methods you want to use within your view explicitly. As an example:
helper_method :get_hotel_recommendation
def get_hotel_recommendation
# ...
end
Helper methods can also be located inside the associated helper module, and it's a good idea to put them there if they're used exclusively within views.
In your case, if you're using this only once, you should probably skip the assignment to an instance variable and just return the object in question. The view would look like:
<%= recommended_hotel_name %>
The adjusted controller method:
class RecommendHotelController < ApplicationController
protected
helper_method :recommended_hotel_name
def recommended_hotel_name
Member.recommended_hotel_name(params[:name], params[:date])
end
end
It's worth noting that declaring #recommended_hotel_name = '' in the class context is probably not what you intend. This creates a class variable, not an instance variable. Instance variables in controllers must be defined inside the primary action method or inside a before_filter method. Also remember that instance variables are nil by default, so there's no need to initialize them to that first. An empty string and nil are equivalent when used within a view, everything inside <%= ... %> is converted to a string for you automatically.
Another thing to watch out for is leaving a space between a method name and its arguments. It should be form_tag(...) and not form_tag (...). Normally this does not make a difference, Ruby can be very lenient, but sometimes it can subtly alter the way the arguments are interpreted leading to a lot of confusion as you try to diagnose the problem. Stylistically speaking, only keywords like if, while and case have a space before the brackets as these are not method calls.
As to why your form isn't working, it's not clear. Those parameters should be submitted as you intend, but maybe you're not getting the right routing. Remember it's best to stick with the standard index, new, show, and edit names unless you're doing something exotic. In this case, you should probably define this as index if it shows more than one record or show if it's always one record.
You can get all the information from a form submit in the params array. However, from your example, I would advise to use: form_for instead of using separate form_tags.
I found a gazillion answers for my issue if I was using Mongo, but none of the ones I see work out here since I am not using mongo.
Basically I have a report_controller.rb that has a very simple method defined:
def donations_by_season
#donations = Donation
end
and a very simple report/donations_by_season.html.erb as follows:
<%= form_for #donations do |f| %>
Stuff Will go here... such as fields to select a date for the season we wish to view.
<% end %>
There is no report model, just a controller and views.
But when I attempt to view /reports/donations_by_season
I immediately get:
undefined method to_key' for #<Class:0x00000114d85918>
What should I do to fix that? Am I doing my form incorrectly since there is no model associated with reports?
You should never be assigning an instance variable to point to a class object like this. You probably want this:
def donations_by_season
#donations = Donation.all
end
Note the .all versus just leaving it blank. You could also do .new or a litany of other methods, depending on what you're trying to do.
On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.
I have the following code in a layout:
Posted <%=time_ago_in_words post.created_at %> ago
<% if post.has_tag != nil %>
in the <%= post.get_first_tag.name %> category
<% end %>
And the following code in the post model which is inheriting form ActiveRecord::Base
def has_tag
!self.tags.empty?
end
def get_first_tag
self.tags[0]
end
Tags is also inherited from ActiveRecord::Base and Post 'has_many' Tags
Firstly: Is this the best way of checking if the post object has at least 1 associate tag attribute.
Secondly: Should I be putting this logic into a helper method?
Thirdly: Why doesn't the following work (it returns a # where the tags should be):
in the <%= post.tags.to_sentence %> category,
I guess its because tags aren't actually stored as an array attribute, but i don't really know.
This is a perfectly good way of checking if there are tags or not. However, self.tags.empty? will return true or false so post.has_tag will never be nil.
It's worth noting that, in ruby, it is common to name methods that return true or false with a question mark. So post.has_tag? would be a better name for your method (like the empty? method for the tags).
This sort of method belongs in the model class rather than a helper as it is not specific to the view layer; you might want to call this method from other model classes, for example.
The reason you are getting # instead of your tag names is that you are trying to convert a collection of tags to a sentence and you need instead to convert the names of the tags to a sentence. You should be able to do
post.tags.map(&:name).to_sentence
which will take the names of the tags and turn them into a sentence.
For one thing, you probably need
<% if post.has_tag %>
instead of
<% if post.has_tag != nil %>
In your definition, has_tag should never return nil, and thus 'in the...' part will always be shown.
Generally, your idea seems fine to me: I often add helpers like these to models.
I want to show a post author's name; <% #post.author.name %> works unless author is nil. So I either use unless #post.author.nil? or add a author_name method that checks for nil as in <% #post.author_name %>. The latter I try to avoid.
The problem is that I may need to add/remove words depending on whether there is a value or not. For instance, "Posted on 1/2/3 by " would be the content if I simply display nil. I need to remove the " by " if author is nil.
Null object pattern is one way to avoid this. In your class:
def author
super || build_author
end
This way you will get an empty author no matter what. However, since you don't actually want to have an empty object sometimes when you do expect nil, you can use presenter of some kind.
class PostPresenter
def initialize(post)
#post = post
end
def post_author
(#post.author && #post.author.name) || 'Anonymous'
end
end
Another way is using try, as in #post.author.try(:name), if you can get used to that.
You can use try:
<%= #post.author.try(:name) %>
It will attempt to call the name method on #post.author if it is non-nil. Otherwise it will return nil, and no exception will be raised.
Answer to your second question: In principle there is nothing wrong with the following:
<% if #post.author %>
written by <%= #post.author.name %>
<% end %>
or
<%= "written by #{#post.author.name}" if #post.author %>
But if this is a recurring pattern, you might want to write a helper method for it.
# app/helpers/authors_helper.rb or app/helpers/people_helper.rb
class AuthorsHelper
def written_by(author)
"written by #{author.name}" if author
end
end
# in your views
<%= written_by(#post.author) %>
Write a method which accepts any variable and checks to see if it is nuil first, and if it is not displays it. Then you only have to write one method.
I found your question interesting as I have often come across similar situations, so I thought I'd try out making my first Rails plugin.
I'm afraid I haven't put in any tests yet but you can try it out http://github.com/reubenmallaby/acts_as_nothing (I'm using Ruby 1.9.1 so let me know if you get any problems in the comments or on Github!)