I always get great big lines of code at the top of my Rails models. I am looking for suggestions for the best way to break them up with standard Ruby style. For example, one line I am looking at now is this:
delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true
What is the conventional style for breaking up these long method call lines?
The short answer is it depends.
Basics
For a start you can save few chars using the "new" Ruby hash syntax:
result = very_long_method_name(something: 1, user: user, flange_factor: 1.34)
vs.
result = very_long_method_name(:something => 1, :user => user, :flange_factor => 1.34)
Hash/Arrays
Sometimes you need to initialize an array or hash, especially for hashes it's nice to write them this way:
args = {
first_name: "Aldo",
email: "nospam#mail.example.com",
age: Float::INFINITY
}
The same hash on the same line would be (not as nice):
args = {first_name: "Aldo", email: "nospam#mail.example.com", age: Float::INFINITY}
Various method calls
Some methods require many params or these params have long names:
%table
%thead
%th
%td= t("first_name", scope: "activemodel.lazy_model.not_so_active_model", some_interpolation_argument: "Mr.", suffix: "(Jr.)")
In this case I would probably write it this way:
%table
%thead
%th
%td= t("first_name",
scope: "activemodel.lazy_model.not_so_active_model",
some_interpolation_argument: "Mr.",
suffix: "(Jr.)")
It's still not very beautiful but I guess is less ugly.
class person < ActiveRecord::Base
validates :n_cars, numericality: {
only_integer: true,
greater_than: 2,
odd: true,
message: t("greater_than_2_and_odd",
scope: "activerecord.errors.messages")
}
end
Again, not the most beautiful code on earth but It has some kind of structure.
Also, sometimes you could use variables to split lines. This is just an example, but basicly you name blocks of things (and sometimes after this you realise you can actually move that block in a method)
class person < ActiveRecord::Base
NUMERICALITY_OPTS = {
only_integer: true,
greater_than: 2,
odd: true,
message: t("greater_than_2_and_odd", scope: "activerecord.errors.messages")
}
validates :n_cars, numericality: NUMERICALITY_OPTS
end
Blocks
Speaking of blocks (closures):
User.all.map { |user| user.method_name }
can be written like this:
User.all.map(&:method_name)
If you have proper blocks try to use do-end instead of curly braces:
nicotine_level = User.all.map do |user|
user.smoker? ? (user.age * 12.34) : 0.1234
end
Conditional
Don't use the ternary if operator for complex things:
nicotine_level = user.smoker? ? (user.age * 1.234 + user.other_method) : ((user.age - 123 + user.flange_factor) * 0)
if user.smoker?
nicotine_level = user.age * 1.234 + user.other_method
else
nicotine_level = (user.age - 123 + user.flange_factor) * 0
end
If you have complex if statements like this:
if user.vegetarian? && !user.smoker? && (user.age < 25) && (user.n_girlfriends == 0) && (user.first_name =~ /(A|Z)[0-1]+/)
end
It's probably just better to move things in methods and make things not only shorter but also readable:
if user.healthy? && user.has_a_weird_name?
# Do something
end
# in User
def healthy?
vegetarian? && !smoker? && (age < 25) && (n_girlfriends == 0)
end
def user.has_a_weird_name?
user.first_name =~ /(A|Z)[0-1]+/
end
Long strings
Heredoc is your friend...I always need to google in order to get the syntax right but once you get it right is makes certain things nicer to read:
execute <<-SQL
UPDATE people
SET smoker = 0
OK, this is a very bad example.
SQL
Queries
I tend to do this way for simple cases:
# Totally random example, it's just to give you an idea
def cars_older_than_n_days(days)
Car.select("cars.*, DATEDIFF(NOW(), release_date) AS age")
.joins(:brand)
.where(brand: {country: "German"})
.having("age > ?", days)
end
Sometimes queries are even worst. If I use squeel and the query is very big I tend to use parenthesis like this:
# Again, non-sense query
Person.where {
first_name = "Aldo" |
last_name = "McFlange" |
(
age = "18" &
first_name = "Mike" &
email =~ "%#hotmail.co.uk"
) |
(
person.n_girlfriends > 1 &
(
country = "Italy" |
salary > 1_234_567 |
very_beautiful = true |
(
whatever > 123 &
you_get_the_idea = true
)
)
)
}
I'd say, if possible try to avoid complex queries and split them in smaller scopes or whatever:
scope :healthy_users, lambda {
younger_than(25).
without_car.
non_smoking.
no_girlfriend
}
scope :younger_than, lambda { |age|
where("users.age < ?", age)
}
scope :without_car, lambda {
where(car_id: nil)
}
scope :non_smoking, lambda {
where(smoker: false)
}
scope :no_girlfriend, lambda {
where(n_girlfriends: 0)
}
This would be probably the best way.
The reality
Unfortunately people tend to write long lines and it's bad:
Long lines are difficult to read (There is a reason if printed books don't have super-large pages)
It's true we mostly use 2 screens but when using things like git diff from the console having long lines is painful
Sometimes you work on your 13" laptop with less screen estate
Even if I love to work with 2 screens I like to split my editor to edit 2 files at the same time - long lines force me to use the horizontal scrollbar (most hated thing on earth)
Yes, you can enable word wrapping in your editor but it's still not as nice (IMHO)
I have a ruler in my editor so that I know when I'm about to cross the 80th char on the line.
But rarely cross the line by few chars it's actually nicer than split it.
Conclusion
There are several ways of keep lines under the 80s and often depends on the situation.
The problem with long lines is not just bad style, long lines are often a symptom of too much complexity.
Something along the lines of:
delegate :occupation, :location, :picture_url,
:homepage_url, :headline, :full_name,
:to => :profile, :prefix => true, :allow_nil => true
Or if you like to highlight the option hash (a reasonable thing):
delegate :occupation, :location, :picture_url,
:homepage_url, :headline, :full_name,
:to => :profile, :prefix => true, :allow_nil => true
The idea of leaving that all on a single line strikes me as a craptastic idea, it means you have to scroll an arbitrary amount in order to see what's being delegated. Ew.
I'd probably line stuff up a bit, too, maybe alphabetize.
delegate :full_name, :headline, :homepage_url,
:location, :occupation, :picture_url,
:to => :profile, :prefix => true, :allow_nil => true
If the file didn't have much/any other substantive content, I might put each method symbol on its own line, just to make editing easier. In a larger file, I wouldn't want to take up the space for that.
Edit 2022: I’d probably put each symbol on its own line, including the options. In the long run, it’s just easier to deal with, and I’ve grown lazier.
Not that I ever think about this kind of stuff.
Edit I guess I do :/
These days I might group the delegated methods by "similarity", roughly:
delegate :full_name, :headline,
:location, :occupation,
:homepage_url, picture_url,
to: :profile, prefix: true, allow_nil: true
My jury's hung on 1.9 hash syntax when the value is also a symbol; I think it looks funny. I'm also not sure where I'd indent it to–might lose it anyway during an IDE reformatting, but I kind of like how it looks above if I'm using the new syntax.
Although the question already has two great answers I'd like to refer future readers to the Ruby Style Guide for such matters.
Currently the Source Code Layout section has plenty of information on how to break lines in various situations:
# starting point (line is too long)
def send_mail(source)
Mailer.deliver(to: 'bob#example.com', from: 'us#example.com', subject: 'Important message', body: source.text)
end
# bad (double indent)
def send_mail(source)
Mailer.deliver(
to: 'bob#example.com',
from: 'us#example.com',
subject: 'Important message',
body: source.text)
end
# good
def send_mail(source)
Mailer.deliver(to: 'bob#example.com',
from: 'us#example.com',
subject: 'Important message',
body: source.text)
end
# good (normal indent)
def send_mail(source)
Mailer.deliver(
to: 'bob#example.com',
from: 'us#example.com',
subject: 'Important message',
body: source.text
)
end
# bad - need to consult first line to understand second line
one.two.three.
four
# good - it's immediately clear what's going on the second line
one.two.three
.four
And it often turns out to be a «solution» for overly complex code as #Aldo already have mentioned:
# bad
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
# good
if some_condition
nested_condition ? nested_something : nested_something_else
else
something_else
end
From my experience, it seems like the convention is actually not to break up the lines. Most projects I've seen, including the codebase of rails itself, seem to have no problem with having really long unbroken lines.
So I'd say if you want to follow convention, don't break up the lines. If you are determined to break the lines, then there's no widely followed convention for how to do it. You can use whichever coding style that you prefer.
Related
I'm trying to build an application on RoR that uses MongoDB via Mongoid for the main objects but has a like and dislike process using Redis via Opinions https://github.com/leehambley/opinions/.
It sort of works but when I run the methods on my objects I just get an error "undefined method `like_by'" where I think the methods are supposed to be autogenerated.
My model looks like:
class Punchline
include Mongoid::Document
include Opinions::Pollable
opinions :like, :dislike
field :key, type: String
field :text, type: String
field :won, type: Boolean
field :created, type: Time, default: ->{ Time.now }
field :score, type: Integer
index({ key: 1 }, { unique: true, name: "key_index" })
belongs_to :user
embedded_in :joke
end
and I run:
user = User.find(session[:userid])
#joke.punchlines.sample.like_by(user);
But it fails with the undefined method error :(
Do I need to initialize Opinions somewhere beyond
/config/initializers/opinions.rb
Opinions.backend = Opinions::RedisBackend.new
Redis.new(:host => 'localhost', :port => 6379)
So, it turns out that Opinions doesn't really work. Why is a bit beyond my two weeks with Rails :)
Anyway, it turns out that this is really easy to do by hand anyway especially as I only had a like and dislike to handle.
I used a Redis sorted set which allows a unique key - value pair with a score. I used a score of +1 or -1 to denote like or dislike and then encoded the key to represent the liked object and the value to be the user id.
This looked like:
def like(user)
$redis.zadd('joke:'+self.key+':likes', 1, user._id)
end
def dislike(user)
$redis.zadd('joke:'+self.key+':likes', -1, user._id)
end
def numLikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',1,1);
return res.count
end
def numDislikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',-1,-1);
return res.count
end
def likedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == 1)
end
def dislikedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == -1)
end
** update **
it all seems to be related to a custom validator: if I remove it, it works as expected. see code at the end
**
I have a model budget that has many multi_year_impacts
in the console, if I run:
b = Budget.find(4)
b.multi_year_impacts.size #=> 2
b.update_attributes({multi_year_impacts_attributes: {id: 20, _destroy: true} } ) #=> true
b.multi_year_impacts.size #=> 1 (so far so good)
b.reload
b.multi_year_impacts.size #=> 2 What???
and if before b.reload I do b.save (which shouldn't be needed anyway), it's the same.
Any idea why my child record doesn't get destroyed?
Some additional information, just in case:
Rails 3.2.12
in budget.rb
attr_accessible :multi_year_impacts_attributes
has_many :multi_year_impacts, as: :impactable, :dependent => :destroy
accepts_nested_attributes_for :multi_year_impacts, :allow_destroy => true
validates_with MultiYearImpactValidator # problem seems to com from here
in multi_year_impact.rb
belongs_to :impactable, polymorphic: true
in multi_year_impact_validator.rb
class MultiYearImpactValidator < ActiveModel::Validator
def validate(record)
return false unless record.amount_before && record.amount_after && record.savings
lines = record.multi_year_impacts.delete_if{|x| x.marked_for_destruction?}
%w[amount_before amount_after savings].each do |val|
if lines.inject(0){|s,e| s + e.send(val).to_f} != record.send(val)
record.errors.add(val.to_sym, " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts")
end
end
end
end
it might depend on your rails version, however, comparing your code to the current docs:
Now, when you add the _destroy key to the attributes hash, with a
value that evaluates to true, you will destroy the associated model:
member.avatar_attributes = { :id => '2', :_destroy => '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
Note that the model will not be destroyed until the parent is saved.
you could try with:
b.multi_year_impacts_attributes = {id: 20, _destroy: true}
b.save
So it looks like the culprit was here
if lines.inject(0){|s,e| s + e.send(val).to_f} != record.send(val)
record.errors.add(val.to_sym, " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts")
end
changing this to the slightly more complex
total = 0
lines.each do |l|
total += l.send(val).to_f unless l.marked_for_destruction?
end
if total != record.send(val)
record.errors[:amount_before] << " please check \"Repartition per year\" below: the sum of all lines must be equal of total amounts"
end
solved the problem.
This one fails when a zero is at the end
12.12 passes
5.51 passes
12.50 fails
12.60 fails
price_regex = /^\d+(\.\d{2})?$/
why? and how do I fix it?
Some more info
in _form.html.erb
<p>
<%= f.label :price %><br />
<%= f.text_field :price %>
</p>
in menu_item.rb
price_regex = /^\d+(\.\d{2})?$/
validates :price, :presence => true,
:format => { :with => price_regex }
in menu_items_controller.rb
def create
#menu_item = MenuItem.new(params[:menu_item])
if #menu_item.save
respond_with #menu_item, :location => menu_items_url
else
flash[:notice] = "Not Saved"
end
end
price is a decimal in the database with a precision of 2.
You say that price is "a decimal in the database with a precision of 2". That means that price is being represented as a BigDecimal in Ruby and the regex test will be done on the string form of that BigDecimal. A little bit of experimentation will clarify things:
> p = BigDecimal.new('12.50')
=> #<BigDecimal:12a579e98,'0.125E2',18(18)>
> p.to_s
=> "12.5"
And so your regex will fail. You shouldn't be using a regex for this at all, regexes are meant for strings but you're checking a number. You should be able to keep using your regex if you allow for the conversion:
/^\d+(\.\d{1,2})?$/
I'm using Rails 3 with the client_side_validations gem, which means I need a Regexp that works both in Ruby and Javascript. I also have a clear delineation between frontend and backend format--The user should never be able to enter "$12.5", but once it hits the server, I don't care about the trailing 0.
My solution was to add a core extension (in my case, for Float, but BigDecimal would probably be more appropriate in most cases):
class Float
def can_convert_to_i_with_no_loss_of_precision
(self % 1).zero?
end
alias_method :to_s_with_loss_of_trailing_zeroes, :to_s
def to_s
if can_convert_to_i_with_no_loss_of_precision
to_i.to_s
else
"#{to_s_with_loss_of_trailing_zeroes}#{0 if (self * 10 % 1).zero?}"
end
end
end
Now I can use this in a Model, and it plays nicely on the front end (Javascript doesn't convert it to a Float, so the user will always be forced to enter 2 digits after the decimal) and on the backend (where ActiveModel's FormatValidator will call to_s and the core extension will know when to add the trailing 0):
validates :price, :format => { :with => /^\d+(\.\d{2})?$/, :allow_blank => true }
The regex looks fine to me. I tested it at Rubular with the inputs you mentioned and a few more, and it captures all of them correctly.
The problem is likely with some other part of the code. Maybe you are using price = <regex>, whereas you should be using price =~ <regex> to match a string with a regex.
Given a model:
class Person
validates_lenght_of :name, :maximum => 50
end
I have some view code that shows a countdown and enforces this maximum. However I hard coded the number 50 into that view code. Is there a way to extract this number from the model?
Something like:
Person.maximum_length_of_name
I tried this:
Person.validators_on(:name)
=> [#<ActiveRecord::Validations::UniquenessValidator:0x000001067a9840 #attributes=[:name], #options={:case_sensitive=>true}, #klass=Trigger(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer, slug: string, last_update_by: integer)>, #<ActiveModel::Validations::PresenceValidator:0x000001067a6c30 #attributes=[:name], #options={}>, #<ActiveModel::Validations::LengthValidator:0x000001067a3f08 #attributes=[:name], #options={:tokenizer=>#<Proc:0x00000101343f08#/Users/sjors/.rvm/gems/ruby-1.9.2-p0/gems/activemodel-3.0.6/lib/active_model/validations/length.rb:9 (lambda)>, :maximum=>50}>]
The information is in there, but I don't know how to extract it:
Use validators_on method
irb(main):028:0> p Person.validators_on(:name)[0].options[:maximum]
50
=> 50
As #Max Williams mentioned it works only on Rails 3
The problem with #nash answer is that validators do not own a certain order. I figured out how to do the same thing with just some more code but in some kind of safer mode ('cause you can add more validators later and break the order you get it):
(Person.validators_on(:name).select { |v| v.class == ActiveModel::Validations::LengthValidator }).first.options[:maximum]
I think it does only work for Rails 3 too.
[Edit 2017-01-17] Carefull my answer is old (2012) and was for Rails 3. It may not work / be ideal for newer Rails versions.
Just to bring a little more DRY spirit, you could create a generic class method to get maximum "length_validator" value on any attribute, like so:
Create a module in your lib directory and make it extend ActiveSupport::Concern:
module ActiveRecord::CustomMethods
extend ActiveSupport::Concern
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecord::CustomMethods)
Add the "module ClassMethods" in it and create the "get_maximum" method:
module ActiveRecord::CustomMethods
extend ActiveSupport::Concern
module ClassMethods
def get_maximum(attribute)
validators_on(attribute).select{|v| v.class == ActiveModel::Validations::LengthValidator}.first.options[:maximum]
end
end
end
EDIT 1: Configuration
You'll also have to add a require in one of your initializers.
For instance, here are my configurations:
I've put my file in lib/modules/active_record/extensions.
I've added this in my autoload_paths: config.autoload_paths +=
%W(#{config.root}/lib/modules) Note: this is not required, but best practice if you want to put there some of your custom classes and modules that you share between your apps.
In one of my initializers (config/initializers/custom.rb) I've added this line: require "active_record/extensions"
And that should do it! Restart your server and then...
END EDIT 1
And then you should be able to do something like this:
<%= form_for #comment do |f| %>
<%= f.text_area(:body, :maxlength => f.object.class.get_maximum(:body)) #Or just use Comment.get_maximum(:body) %>
<% end %>
I hope it will help others! :) Of course you can customize the method the way you want and add options and do fancy stuff. ;-)
More concise:
Person.validators_on(:name).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:maximum]
Uses detect{} instead of select{}.first and is_a? instead of class ==.
That works with Rails 4.1 as well.
Even more dynamic than Kulgar's answer would be to use something like this:
Person.validators_on(attribute)
.select { |v| v.class == ActiveRecord::Validations::LengthValidator }
.select { |v| v.options[:maximum].present? }.first.options[:maximum]
That way you can order the validators inside the model the way you want.
Use it as a Rails helper
You then could use this code to write a helper:
# Returns the maximum length for a given attribute of a model
def get_maxlength(model, attribute)
model.validators_on(attribute)
.select { |v| v.class == ActiveRecord::Validations::LengthValidator }
.select { |v| v.options[:maximum].present? }.first.options[:maximum]
end
And utilize the helper like this:
= f.text_field :name, maxlength: get_maxlength(f.object.class, :name) # Or use get_maxlength(Person, :name)
This is an indirect answer, but is an alternative solution, just in case it may prove useful to anyone.
Alternative Solution 1
class Person
MAX_LENGTHS = {
name: 50,
# email: 30, etc...
}.freeze
validates :name, length: { maximum: MAX_LENGTHS.fetch(:name) }
end
# usage example in view file
<%= Person.MAX_LENGTHS.fetch(:name) %>
Alternative Solution 2
... or if you prefer one-liners, or to not use a Hash constant
class Person
validates :name, length: { maximum: (MAX_NAME_LENGTH = 50) }
# validates :password, length: {
# minimum: (MIN_PASSWORD_LENGTH = 8),
# maximum: (MAX_PASSWORD_LENGTH = 70)
# }
# ... etc
end
# usage example in view file
<%= Person.MAX_NAME_LENGTH %>
If you're in rails 2, it's not easy. I remember trying this before and not getting far. You can get at the validation objects but they don't list which field they are for which seemed very odd to me. People have done plugins for this (ie to inspect or reflect on an AR object's validations), such as https://github.com/redinger/validation_reflection
Kulgar's answer is good and possibly ideal. Here is an easier way to do this for Rails 4 that does not require modifying any configuration, with the disadvantage that you have to add an include line to every model that wants to use it.
models/concerns/introspection.rb:
module Introspection
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def max_length(property)
Resource.validators_on(property.to_sym).
select{ |v| v.kind_of?(ActiveModel::Validations::LengthValidator) }.
first.options[:maximum]
end
end
end
Then in your model:
class Comment < ActiveRecord::Base
include Introspection
..
end
Then you can do something like this:
<%= form_for #comment do |f| %>
<%= f.text_area(:body, :maxlength => f.object.class.max_length(:body)) %> # Or just use Comment.max_length(:body) %>
<% end %>
hiho
Is there any way to tell rails that my string may not be 'something'?
I am searching for something like
validates :string, :not => 'something'
thanks
klump
Either of these will do the job (click on the methods for documentation):
Probably the best and fastest way, easy to extend for other words:
validates_exclusion_of :string, :in => %w[something]
This has a benefit of using a regexp, so you can generalise easier:
validates_format_of :string, :without => /\A(something)\Z/
You can extend to other words with /\A(something|somethingelse|somemore)\Z/
This is the general case with which you can achieve any validation:
validate :cant_be_something
def cant_be_something
errors.add(:string, "can't be something") if self.string == "something"
end
To get exactly the syntax you proposed (validates :string, :not => "something") you can use this code (a warning though, I discovered this while reading the master branch of the rails source and it should work, but it doesn't work on my ~ 3 months old install). Add this somewhere in your path:
class NotValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << "must not be #{options{:with}}" if value == options[:with]
end
end
A couple of ways. If you have exact list of what it can't be:
validates_exclusion_of :string, :in => ["something", "something else"]
If you want to ensure that it doesn't exist as a substring at all:
validates_format_of :string, :with => /\A(?!something)\Z/
If it is more complicated and you want to hide the messy details:
validate :not_something
def not_something
errors.add(:string, "Can't be something") if string =~ /something/
end