Hidden Features of Ruby on Rails [closed] - ruby-on-rails

As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
As a companion to Hidden features of Ruby.
Try to keep it to Rails since the other is a better place for Ruby-specific examples. One per post please.

To avoid duplicate form submissions, Rails has a nice option for submit tags:
submit_tag "Submit", :disable_with => "Saving..."
This adds behavior to the submit button to disable it once clicked, and to display "Saving..." instead of "Submit".
Rails 4+
DEPRECATION WARNING: :disable_with option is deprecated and
will be removed from Rails 4.1. Use 'data: { disable_with: 'Text' }' instead.
Thus the above becomes:
submit_tag 'Submit', data: { disable_with: 'Text' }

integer.ordinalize is one little method that I just stumbled upon not to long ago.
1.ordinalize = "1st"
3.ordinalize = "3rd"

I'm currently in love with div_for and content_tag_for
<% div_for(#comment) do %>
<!-- code to display your comment -->
<% end %>
The above code renders this:
<div id="comment_123" class="comment">
<!-- code to display your comment -->
</div>
Want the CSS class to be comment other_class? No problem:
<% div_for(#comment, :class => 'other_class') do %>
<!-- code to display your comment -->
<% end %>
Want a span and not a div? No problem, content_tag_for to the rescue!
<% content_tag_for(:span, #comment) do %>
<% end %>
# Becomes...
<span id="comment_123" class="comment">
<!-- code to display your comment -->
</span>
content_tag_for is also great if you want to prefix you id. I use it for loading gifs.
<% content_tag_for(:span, #comment, 'loading') do %>
<%= image_tag 'loading.gif' -%>
<% end %>
# Becomes...
<span id="loading_comment_123" class="comment">
<img src="loading.gif" />
</span>

To see a list of gems that are installed, you can run:
gem server
Then point your browser at:
http://localhost:8808
You get a nicely formatted list of your gems with links to rdoc, the web and any dependencies. Much nicer than:
gem list

You can take advantage of the fact that Ruby class definitions are active and that Rails caches classes in the production environment, to ensure that constant data is only fetched from the database when your application starts up.
For example, for a model that represents countries you'd define a constant that performs a Country.all query when the class is loaded:
class Country < ActiveRecord::Base
COUNTRIES = self.all
.
.
.
end
You can use this constant within a view template (perhaps within a select helper) by referring to Country::COUNTRIES. For example:
<%= select_tag(:country, options_for_select(Country::COUNTRIES)) %>

in your environment.rb, you can define new date/time formats e.g.
[Time::DATE_FORMATS, Date::DATE_FORMATS].each do |obj|
obj[:dots] = "%m.%d.%y"
end
so then in your views you can use:
Created: <%= #my_object.created_at.to_s(:dots) %>
which will print like:
Created: 06.21.09

If you have a model with some class methods and some named scopes:
class Animal < ActiveRecord::Base
named_scope 'nocturnal', :conditions => {'nocturnal' => true}
named_scope 'carnivorous', :conditions => {'vegetarian' => true}
def self.feed_all_with(food)
self.all.each do |animal|
animal.feed_with(food)
end
end
end
Then you can call the class methods through the named scope:
if night_time?
Animal.nocturnal.carnivorous.feed_all_with(bacon)
end

Rails 2.3.x now allows you to do:
render #items
much simpler..

I'll start with one of my favorites. When calling a partial with a collection, instead of looping through your collection and calling it for each item, you can use this:
render :partial => 'items', :collection => #items
This will call the partial once per item, and pass a local variable item each time. You don't have to worry about nil checking #items either.

You can change the behaviour of a model for your test suite. Say you have some after_save method defined and you do not want it to happen in your unit tests. This is how it works:
# models/person.rb
class Person < ActiveRecord::Base
def after_save
# do something useful
end
end
# test/unit/person_test.rb
require 'test_helper'
class PersonTest < ActiveSupport::TestCase
class ::Person
def after_save
# do nothing
end
end
test "something interesting" do
# ...
end
end

Funny feature is that array has special method for accessing its 42 element
a = []
a.forty_two
http://railsapi.com/doc/rails-v2.3.8/classes/ActiveSupport/CoreExtensions/Array/Access.html#M003045

If you add routing for a resource:
ActionController::Routing::Routes.draw do |map|
map.resources :maps
end
And register additional mime-types:
Mime::Type.register 'application/vnd.google-earth.kml+xml', :kml
You don't need a respond_to block in your controller to serve these additional types. Instead, just create views for the specific types, for example 'show.kml.builder' or 'index.kml.erb'. Rails will render these type-specific templates when requests for '/maps.kml' or '/maps/1.kml' are received, setting the response type appropriately.

ActionView::Base.default_form_builder = MyFormBuilderClass
Very useful when you're creating your own form builders. A much better alternative to manually passing :builder, either in your views or in your own custom_form_for helper.

The returning block is a great way to return values:
def returns_a_hash(id)
returning Hash.new do |result|
result["id"] = id
end
end
Will return a hash. You can substitute any other types as well.

Get everything printed with rake routes programmatically:
Rails.application.routes

Related

Rails category (or filter) links in same controller?

Having trouble understanding how using links_to filter content within the same controller in the rails view works. My code is below:
# index.html.erb (link nav area)
<nav>
<%= link_to 'Daily Monitoring', root_path(:category => "dailymonitoring") %>
<%= link_to 'Smoke Tests', root_path(:category => "smoketests") %>
</nav>
# index.html.erb (cont.)
<ul id="results">
<% if #reportlinks == "dailymonitoring" %>
# dailymonitoring content
<% elsif #reportlinks == "smoketests" %>
# smoketests content
<% end %> <!-- end conditional -->
</ul>
# reports_controller.rb
if params[:category]
#reportlinks = Report.where(:category => params[:category])
else
#reportlinks = Report.all
end
# model (report.rb)
class Report
include ActiveModel::Model
attr_accessor :reports, :smokereports
belongs_to :reports, :smokereports, :reportlinks
end
The error I'm getting is undefined method `belongs_to' for Report:Class and a < top (required) > error. There's no database involved. I'm just trying to make it known that I want to click on any of the links and that filters only the block of content within that if/else statement.
Do I need to create a new controller for the if/else statement to work? Please let me know if more code is needed to get a better understanding. Thanks.
belongs_to is defined in ActiveRecord::Associations, which is part of ActiveRecord. You are manually including ActiveModel::Model which doesn't offer any association-related capabilities.
Includes the required interface for an object to interact with ActionPack, using different ActiveModel modules. It includes model name introspections, conversions, translations and validations. Besides that, it allows you to initialize the object with a hash of attributes, pretty much like ActiveRecord does.
Assuming you don't need the whole ActiveRecord database persistence capabilities but you need the association style, then you'll need to deal with that manually.
Therefore, you will have to define your own methods to append/remove associated records and keep track of them.
The association feature of ActiveRecord is strictly tied with the database persistence.

method attributes [ajax,jquery,rails4]

I am reading the book Agile web developpment with rails 4.
there is a part where the products' cart is showing only if it is not empty, my question is the function in the view send to the helper only 2 attributes while in the implementation there are 3 parameters.
in the view I have the bellow code, which render to _cart where I have the cart show
<%= hidden_div_if(#cart.line_items.empty?, id: 'cart') do %>
<%= render #cart %>
<% end %>
the helper has:
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)
if condition
attributes["style"] = "display: none"
end
content_tag("div", attributes, &block) end
end
My question is the &block in this case receives id: 'cart' but is it a optional attibute? that why it comes with &. but what about attributes = {}?
I am really not sure how that is happening, could someone explain me a bit?
Thanks!!
The code between and including do and end is the block, and this is the third argument for hidden_div_if, which is simply passed on to content_tag. The & in the definition of hidden_div_if captures the block in your view, whereas the & in the call to content_tag expands it again to pass it along.
The answer here explains this idea nicely with a few examples. I recommend testing everything out yourself in irb to get a feel for it.

Getting text to show up if a condition is true

I'm trying to get the text "Tags:" to show up only if tags are present, so I did the following in my view:
<% if #tags.present? %>
<%= puts "Tags:"%>
<% end %>
Which doesn't work... I'm a beginner, and have no idea what I'm doing wrong.
Thanks
EDIT:
A tag belongs to an Article.
Tags is defined in my Article model as:
def tag_tokens
self.tags.collect{|t| t.name}.join(", ")
end
def tag_tokens=(tags_delimited)
tags_delimited.split(",").each do |string|
self.article_tags.build(:tag => Tag.find_or_create_by_name(string.strip.downcase))
end
end
I'm trying to make it so that when an article has tags the word "Tags:" shows up before the list of tags, and when an article doesn't have any tags, the word "Tags:" doesn't show up.
Right now <% if #tags.nil %> just causes "Tags:" to show up on every post.
You don't use puts in views -- puts causes the text to go to your console. This will fix it:
<% if #tags.present? %>
<%= "Tags:"%>
<% end %>
You also don't need to use .present? by the sound of it. If you only want to see if it's been set, you should use .nil? instead. You can also condense this down to a single line.
<%= "Tags:" unless #tags.nil? %>
UPDATE: It looks like the tag_tokens method is broken for you in both the getter and setter. Your setter isn't actually saving anything by the looks of it (.build returns a new object, you need to save it). Your getter is also referencing tags, instead of article_tags which is what you're trying to save by the looks of it. Changing it to this should work for saving:
self.article_tags.build(:tag => Tag.find_or_create_by_name(string.strip.downcase)).save
This is assuming that you have a line that is something like:
has_many :article_tags
has_many :tags, through: :article_tags
Which I'm assuming you do based on your setter.
I assume this is a many-to-many relationship, but it looks like you're using has_many :through, rather than has_and_belongs_to_many. Is there a reason for this? If you're using has_and_belongs_to_many you should be able to do this:
has_and_belongs_to_many :tags
def tag_tokens=(tags_delimited)
self.tags = []
tags_delimited.split(",").each do |string|
self.tags << Tag.find_or_create_by_name(name: string)
end
end
If you do that, you should not have an ArticleTags model at all, and you should have a table called articles_tags with no primary column, and an article_id and tag_id column.
Update 2:
You're not setting #tags to anything, which is why it's always nil. #tags is a variable, which needs to be set to have a value just like #articles is being set in your controller's index method. Regardless, since this is for an index method, you wouldn't want it to be a single instance variable regardless. You should be accessing your tag_tokens method for that particular instance. app/views/articles/index.html.erb lines 53-55 should be changed to this:
<%= "Tags:" if article.tags.any? %>
Check the answer by sgrif, it contains a lot of good points. To just answer your main question:
In erb (the "language" used for view templates in Rails) you can use <%= ... %> to interpolate the result of some Ruby code into your view template.
When you are doing:
<%= puts "Tags:" %>
the following happens:
Ruby evaluates/executes your code: "Tags: " is printed to STDOUT and nil is returned since a call to puts alsways returns nil
erb interpolates the result into your template, the result is nil, which shows up as "nothing"
To fix it, just use:
<% if #tags.present? %>
<%= "Tags:"%>
<% end %>
or, since you are not doing anything in Ruby, you can just use:
<% if #tags.present? %>
Tags:
<% end %>
What has #tags been defined as? Where do you want to check if it is present?
Do you want if #tags.nil?

Rails - Add a method to a textfield

I'm trying to get the checkSwear method to run on each textfield before it's submitted..
I have basically this: (stripped down)
<%= form_for(#profile) do |f| %>
<div class="field">
<%= f.label 'I love to ' %>
<%= f.text_field :loveTo %>
</div>
<div class="field">
<%= f.label 'I hate to ' %>
<%= f.text_field :hateTo %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In my controller I have:
def checkSwear
antiSwear.checkSwear(What goes here?)
end
In routes:
match '/check' => 'profiles#checkSwear'
Any help much appreciated!
(checkSwear is a separate gem; i.e. a separate problem! The what does here means what kind of variable is received from the form, to be put through the checkswear gem)
UPDATE:
Sorry for the camelcasing, I'm a Java developer studying Rails etc., old habits die hard. This is for a project. I'm supposed to be writing a small gem to do some ruby logic and apply it to something. The contents of the gem are:
module antiSwear
#swearwords = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#replacements = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
#swearwords.each do |swearword|
if text.include?(swearword)
index = #swearwords.index(swearword)
replacement = #replacements[index]
text.gsub(swearword, replacement)
end
end
return text
end
end
:/
This should really be done in model validations.
class Profile < ActiveRecord::Base
validate :deny_swearing
private
def deny_swearing
if AntiSwear.check_swear(love_to) || AntiSwear.check_swear(hate_to)
errors.add_to_base('Swearing is not allowed.')
end
end
end
That said, if you insist on this being in controller, you can check params[:profile][:love_to] and params[:profile][:hate_to] to see what's been submitted.
P.S. In this example I used proper ruby naming conventions, since we don't use "camelCasing".
Are you doing this as part of validation? You can do it one of a few ways. You can run the check before save, via a custom validation method or override the setter directly. I show you the custom validation approach here:
class Profile < ActiveRecord::Base
validate :clean_loveTo
protected
def clean_loveTo
errors.add(:loveTo, "can't contain swears") if antiSwear.checkSwear(loveTo)
end
end
I'm assuming checkSwear returns a boolean here.
I'd use an intersection on arrays, one of which is the source text split into words, then gsub the replacements in. You have to be sure to have a 1:1 relationship between the words and their replacements, in which case I'd suggest using a hash for your dictionary (coincidentally what hashes are sometimes called in other languages).
module antiSwear
# var names changed for formatting
#swears = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#cleans = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
# array intersection. "which elements do they have in common?"
bad = #swears & text.split # text.split = Array
# replace swear[n] with clean[n]
bad.each { |badword| text.gsub(/#{badword}/,#cleans[#swears.index(badword)] }
end
end
You might need to futz with text.split arguments if the replacement gets hung up on \n & \r stuff.

Rails and ActiveRecord: Save a parent object while using find-or-create on has-many children

I am trying make a complex form (like the railscast) with repeated-auto-complete (modified by Pat Shaughnessy) work for creating articles with many authors (has-many :through). I've got it working as long as I willing to always create new authors when I save an article. How can I get my associated author records to only be created when they don't already exist and just get a join table update for when they do?
I know you can you use find-or-create to get this result with the parent object but I need it for the associated objects that are saved when #article.save is called for the article.
in articles.rb
before_save :remove_blank_authors
after_update :save_authors
def remove_blank_authors
authors.delete authors.select{ |author| author.fullname.blank?}
end
def new_author_attributes=(author_attributes)
author_attributes.each do |attributes|
authors.build(attributes)
end
end
def existing_author_attributes=(author_attributes)
authors.reject(&:new_record?).each do |author|
attributes = author_attributes[author.id.to_s]
if attributes
author.attributes = attributes
else
author.delete(author)
end
end
end
def save_authors
authors.each do |author|
author.save(false)
end
end
and the authors partial of my view:
<div class="author">
<% fields_for_author author do |f| %>
<%= error_messages_for :author, :object => author %>
<%= f.label :fullname, "Author Fullname:" %>
<%= f.text_field_with_auto_complete :author, :fullname, {}, {:method => :get } %>
<%= link_to_function "remove", "$(this).up('.author').remove()" %>
<% end %>
</div>
I'm using Rails 2.2.2.
The problem is that I can't see where I could use the find-or-create. At the point where the attributes for the authors are being built - new_author_attributes - I have nothing to search on - that is just pre-building empty objects I think - and at the point where the authors are being saved they are already new objects. Or am I wrong?
It depends on what version of Rails you are using but you should be able to do:
#article.authors.find_or_create_by_article_id(#author)
or
#author.articles.find_or_create_by_author_id(#article)
then Rails "should" fill in the details for you...
In order to use find_or_create you need to have a condition to evaluate it by. That condition (the by_author_id part) can be changed to any column in the Article model.
This is one of the convention features that Rails includes which I haven't been able to find too much info on, so if this is way off, sorry ;)

Resources