Formatting data after collection from the database - ruby-on-rails

In the following way I can format the data when I assign to the object:
def cost=( w )
super w.gsub( ",", "." ).gsub( /[^0-9\.]/, "" ).to_f
end
Is there a simple way to do it the other way?
I mean that when I get the price from the database automatically formats the data like in the example above?

Sure. In your view just use number_with_delimeter, e.g.:
<%= number_with_delimiter #record.cost %>
This assumes you have your Rails locale set to the appropriate local for the formatting you want to use. Otherwise you can force a local, e.g.:
<%= number_with_delimiter #record.cost, :locale => :fr %>
By the way, you could do this in your model by overriding cost, e.g.:
def cost
some_formatting_method self[:cost]
end
...but that would be breaking with MVC principles. It's best, as I said, to do this in your view, with the built-in helpers.

Related

HAML conditional tags

I'm currently building a ruby on rails app using HAML as the template language. I'm looking to create a condition that defines a tag dependent on whether it is met or not, otherwise it defines a different tag. I know I could write this like:
- if ordered
%ol
- else
%ul
But this isn't particularly DRY, and would require me to duplicate the bulk of the code. Is there a very straightforward way to get around this problem? Should I be looking into ruby logic to find it?
Thanks
Define a helper. We'll introduce the ordered option to pick the tag, the rest are passed on to the tag.
# app/helpers/application_helper.rb
module ApplicationHelper
def list_tag(ordered: false, **opts)
kind = ordered ? :ol : :ul
haml_tag kind, **opts do
yield
end
end
end
And then,
-# some_view.html.haml
%p
Here's a list:
- list_tag ordered: false, class: 'some_class' do
- #list.each do |item|
%li
= item
If you need to do this logic in different views I think there are two approaches you can follow:
1. Make a partial and render it where you need this. If you need to pass variables use local_assigns
_my_list.html.haml
- if ordered
%ol
- else
%ul
use it
render 'partials/my_list', ordered: ordered
2. Make your own helper
def my_list(ordered)
if ordered
content_tag(:ol, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end else
content_tag(:ul, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end
end
end
use it
= my_list(ordered)
You can keep your ordered variable outside of the view and deal with the whole logic inside the helper.
If you ask yourself what to use, well the first answer from here is pretty good.
You can use the content_tag method as follows. content_tag documentation
= content_tag(ordered ? "ol" : "ul")
If you need it in several occasions you can put that in a helper method
module Listhelper
def list(ordered, html_options = {})
= content_tag(ordered ? "ol" : "ul")
end
end
Call it from the view with = list(your_variables)

How it's possible to send an array from one view to the next

I have a Controller with the function getAccounts where I look for certain accounts. My idea is to first show the number of results and then send the result array to the next function called showAccounts which generates the view. First of all I declared the result array as an instance variable. Then I tried to send with a form tag. It does not work ... Has anyone an idea?
def getAccounts
filter = '(uid='+params[:id]+')'
attrs = ['*']
#accounts=Array.new
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn=admin, dc=cippool-mb, dc=rwth-aachen, dc=de','DLPins!')
conn.perror("bind")
begin
conn.search($base, $scope, filter, attrs) { |entry|
setAttributes(entry)
}
rescue LDAP::ResultError
conn.perror("search")
exit
end
conn.perror("search")
conn.unbind
end
def showAccounts
end
The view where I send the data.
Es wurden <%= #accounts.size %> Accounts gefunden.
<%= form_tag :action => "showAccounts" do %>
<%= hidden_field_tag "accounts", #accounts %>
<%= submit_tag "Anzeigen" %>
<% end %>
I can also paste the view where I need this array, but I dont't think it's relevant for this question. I use Rails 3.2.7 and Ruby 1.9.2p0
If you want to pass some large amount of data between separate requests I would suggest using session, it's designed for such things.
If you debug(#accounts) you'll see what it passes -- something like <#0x7187237 Array> which is not what you want!
If you really want to pass in the accounts array, you'll need to serialize it to a text format to put in a hidden field. That's going to probably be a HUGE chunk of data though if #accounts is large.
That said, you could dump it to YAML or JSON, or use one of the serialization functions in Ruby or put it into a custom text format of your own (not recommended). Keep in mind then that you need to deserialize on the next page before you use it.
I'm assuming part of the wanting to pass it to the next step is to avoid an expensive LDAP request. You might want to look at putting in a lightweight cache -- redis for example -- to temporarily store the requests.

DRY a large chunk of common code with two vastly different uses

I have a 'widget' that comprises an html/css block of code. It is a type of data layout, which I call the 'stack'.
The stack has bits of .erb (Ruby on Rails) embedded in it, which enters the data for each user.
I need to include this stack in multiple places, where it needs to represent different data from different models.
So, one stack might contain a field called #company.name and the other stack might contain #project.name || "Unidentified Project".
How does one refactor / organize this situation? Options that I can see:
Have two separate stacks, which would introduce redundancy and inconsistency, but would be an obvious answer to the problem without limit to scenario-specific customization.
Include if statements for every data point to test which circumstance the stack is being used for, but this is very code-ugly and unsustainably complicated for more than 2 stacks.
Some unknown unknown.
How would you tackle this?
One simple way would be to write the erb in a generic way so that it works for either a project or a company for example, in your Project model you could put:
def display_name
name || "Unidentified Project"
end
Then in your Company model put:
def display_name
name
end
When you render the ERB, pass in a variable with some generic name like main_object and call its display_name function. The ERB code would not know or care what class main_object is:
<%= main_object.display_name %>
If it bothers you to put display-related functions like display_name in your models, you could use the Presenter pattern. A present is basically a plain-old ruby object that you create from your model(s) and then pass to the view. I saw a good talk on this pattern by Jeff Casimir called "Fat Models Aren't Enough" and the slides are here:
http://en.oreilly.com/rails2011/public/schedule/detail/18514
David's solutions are good. In some situations you may also consider helpers or partials (e.g. if you want to include complex html). For example:
Helper
def display_name(object)
if object.respond_to? :name and object.name
object.name
else
if object.class.respond_to? :human_name
"Unidentified #{object.class.human_name}"
else
"Unidentified #{object.class.name}"
end
end
end
Partial
<%= render :partial => "stack/name/#{object.class.underscore}", :locals => {object.class.underscore => object} %>
With any complex ERB in app/views/stack/name/_project.html.erb:
<label style="<%= "background-color: red;" if project.name.blank? %>">
<%= project.name || "Unidentified Project" %>
</label>

Rails - default value in text_field but only for new_record?

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.

How can I make Rails 3 localize my date formats?

I am working on a Rails 3 project where there is place for date input within a form. The text field with the date uses a date picker so there is no concern about the date being entered in a wrong format, however the date is being displayed in the :db format (e.g. 2010-01-21).
(Note: this is specifically in form fields - e.g. <%= f.text_field :publish_date %>, which should automatically use :default format, and shouldn't need to be provided with a value)
I have tried adding in a customized locale which has the following date configuration:
date:
formats:
# Use the strftime parameters for formats.
# When no format has been given, it uses default.
# You can provide other formats here if you like!
default: "%d/%m/%Y"
short: "%b %d"
long: "%B %d, %Y"
And then setting my locale to this (config.i18n.default_locale = "en-AU") however this doesn't seem to take and its becoming quite frustrating.
The app will eventually support a number of locales, so setting up an initializer to override the date formats at application startup isn't really suitable, and I know that this should work - I'm guessing I've missed something here.
The locale file is: config/locales/en-AU.yml and in my application.rb I am including:
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "*.yml").to_s]
config.i18n.default_locale = "en-AU"
in my application.rb file.
When displaying a date, you can use I18n.l
So you would do :
I18n.l #entry.created_at
And if you want to change it's format :
I18n.l #entry.created_at, :format => :short
The internationalization rails guide is documenting that.
#damien-mathieu has a solid answer for displaying localized dates with I18n.localize, and his comment raises an important caveat: this breaks form text inputs. Since then, rails gives us a nice solution.
As of Rails 5, you can use the
Rails attributes API to customize how user input is transformed into a model or database value. Actually, it was available in Rails 4.2, just not fully documented.
Through Sean Griffin's considerable efforts, all models' types are now defined as ActiveRecord::Type objects. This defines a single source of truth for how an attribute is handled. The type defines how the attribute is serialized (from a ruby type to a database type), deserialized (from a database type to a ruby type) and cast (from user input to a ruby type). This is a big deal, because messing with this used to be a minefield of special cases developers should avoid.
First, skim the attribute docs to understand how to override an attribute's type. You probably need to read the docs to understand this answer.
How Rails Transforms Attributes
Here's a quick tour of the Rails Attributes API. You can skip this section, but then you won't know how this stuff works. What fun is that?
Understanding how Rails handles user input for your attribute will let us override only one method instead of making a more complete custom type. It will also help you write better code, since rails' code is pretty good.
Since you didn't mention a model, I'll assume you have a Post with a :publish_date attribute (some would prefer the name :published_on, but I digress).
What is your type?
Find out what type :publish_date is. We don't care that it is an instance of Date, we need to know what type_for_attribute returns:
This method is the only valid source of information for anything related to the types of a model's attributes.
$ rails c
> post = Post.where.not(publish_date: nil).first
> post.publish_date.class
=> Date
> Post.type_for_attribute('publish_date').type
=> :date
Now we know the :publish_date attribute is a :date type. This is defined by ActiveRecord::Type::Date, which extends ActiveModel::Type::Date, which extends ActiveModel::Type::Value. I've linked to rails 5.1.3, but you'll want to read the source for your version.
How is user input transformed by ActiveRecord::Type::Date?
So, when you set :publish_date, the value is passed to cast, which calls cast_value. Since form input is a String, it will try a fast_string_to_date then fallback_string_to_date which uses Date._parse.
If you're getting lost, don't worry. You don't need to understand rails' code to customize an attribute.
Defining a Custom Type
Now that we understand how Rails uses the attributes API, we can easily make our own. Just create a custom type to override cast_value to expect localized date strings:
class LocalizedDate < ActiveRecord::Type::Date
private
# Convert localized date string to Date object. This takes I18n formatted date strings
# from user input and casts them back to Date objects.
def cast_value(value)
if value.is_a?(::String)
return if value.empty?
format = I18n.translate("date.formats.short")
Date.strptime(value, format) rescue nil
elsif value.respond_to?(:to_date)
value.to_date
else
value
end
end
end
See how I just copied rails' code and made a small tweak. Easy. You might want to improve on this with a call to super and move the :short format to an option or constant.
Register your type so it can be referenced by a symbol:
# config/initializers/types.rb
ActiveRecord::Type.register(:localized_date, LocalizedDate)
Override the :publish_date type with your custom type:
class Post < ApplicationRecord
attribute :publish_date, :localized_date
end
Now you can use localized values in your form inputs:
# app/views/posts/_form.html.erb
<%= form_for(#post) do |f| %>
<%= f.label :publish_date %>
<%= f.text_field :publish_date, value: (I18n.localize(value, format: :short) if value.present?) %>
<% end %>
check out the delocalize gem, it might help you out some.
https://github.com/clemens/delocalize
http://www.railway.at/articles/2009/05/03/new-plugin-delocalize/
What I found to be the best solution is this:
I localize date formats in my locale file like you do
In my forms I localize the date by setting the value directly
<%= f.text_field :publish_date, :value => (#model.publish_date.nil? ? nil : l(#model.publish_date)) %>
It is not perfect sadly, but at least this way I can use my form for both new and existing records. Also the app will stay compatible with multiple locales compared to changing the default format with initializers. If you fully want to comply with DRY you could always write a custom helper.
You could override your getters for the :publish_date attribute.
i.e. in your model:
def date( *format)
self[:publish_date].strftime(format.first || default)
end
and in your view you could do either
#income.date("%d/%m/%Y")
or
#income.date
This would cause strftime to use the passed format string unless it was nil, in which case it would fall back to your default string.
Note: I used the splat operator to add support for getting the date without an argument. There may be a cleaner way to do that.

Resources