In views/products/list.html.erb I use:
<%= product.power.power_in_kw.to_kw if ... %>
to_kw is defined in lib/my_extensions.rb along with other methods:
class Symbol
def pluralize
to_s.pluralize.to_sym
end
end
class BigDecimal
def to_kw
number_to_currency(self, :unit => "kw", :format => "%n%u", :precision => 1)
end
end
class Float
def to_dollar
number_to_currency(self)
end
end
config/environment.rb has the following line at the end:
require 'my_extensions'
However, I got the following error:
undefined method `to_kw' for #<BigDecimal:2704620,'0.555E2',8(8)>
What am I missing ?
I know it's been hours since you submitted this, but these functions might work once you restart your app. Items in lib generally are not reloaded automatically like those in app, so changes made will not be reflected in the application until performing a full restart.
Just throwing it out there :)
I'm also going to point out that, once you have these methods up and running, they probably will not work immediately. This is because your views are defined in the context of all of the Rails view helpers, like ActionView::Helpers::NumberHelper, which defines number_to_currency. Your extension in lib, however, is not defined in such a context, and therefore cannot access those helpers.
ActionView::Helpers::NumberHelper.number_to_currency might be more likely to work as expected.
You should include ActionView::Helpers::NumberHelper in your BigDecimal and Float:
class BigDecimal
include ActionView::Helpers::NumberHelper
def to_kw
number_to_currency(self, :unit => "kw", :format => "%n%u", :precision => 1)
end
end
class Float
include ActionView::Helpers::NumberHelper
def to_dollar
number_to_currency(self)
end
end
I think the error undefined method to_kw is caused by the undefined method number_to_currency.
Related
I am using "number_with_precision" method in Rails View, it is working fine there, for example:
In Rails View: (works fine)
<%= number_with_precision(job.company.rating, :precision => 2) %>
But when I try to do the same in ApplicationHelper's method then it gives me the following error:
undefined method `number_with_precision' rails
Here is what I have tried:
In Rails ApplicationHelper: (gives error)
module ApplicationHelper
def employer_overall_rating(overall_rating)
#overall_rating = number_with_precision(overall_rating, :precision => 2)
end
end
How can I use the same in ApplicationHelper?
include ActionView::Helpers::NumberHelper in your application_helper.rb
If you don't want to clutter the namespace with number helpers, one way of doing that is calling the helper through ActionController::Base.helpers.
def employer_overall_rating(overall_rating)
#overall_rating = ActionController::Base.helpers.number_with_precision(overall_rating, :precision => 2)
end
Naturally that would be simplified by:
def helpers
ActionController::Base.helpers
end
Then you could do just helpers.number_with_precision
Another way is to include ActionView::Helpers::NumberHelper
Try Following
module ApplicationHelper
include ActionView::Helpers::NumberHelper
def employer_overall_rating(overall_rating)
#overall_rating = number_with_precision(overall_rating, :precision => 2)
end
end
There seems to be some people that have gotten Paperclip working on a regular ruby class by doing something like the following:
require "paperclip"
Class Person
include Paperclip
has_attached_file :avatar,{}
end
See here
This does not work for me even when using the main Paperclip repo:
$ bundle exec rails c
>> Rails.version
=> "3.1.3"
>> require 'paperclip'
=> false
>> class Monkey
>> include Paperclip
>> has_attached_file :avatar,{}
>> end
NoMethodError: undefined method `has_attached_file' for Monkey:Class
Has anyone gotten this working and can possibly give a clue on what could be going wrong?
Thanks!
Paperclip is pretty explicitly for use with AR.
Another option is to use carrier wave instead which works pretty well outside AR, with a variety of ORMS, or none:
https://github.com/jnicklas/carrierwave
I recently had to figure this out. You need to use some ActiveModel stuff in defining your library class or model. Specifically, to use Paperclip, you need the following methods: save, destroy, their callbacks, to_key (for use with form_for), attr_acessors for id, and of course, *_file_name, *_file_size, *_content_type, *_updated_at for each attached file.
The following class should give you the minimum implementation you need. This "solution" uses Rails 3.2.8, Ruby 1.9.3, and Paperclip 3.2.0 as of Sept 10, 2012, although other configurations may work.
class Importer
extend ActiveModel::Callbacks
include ActiveModel::Validations
include Paperclip::Glue
define_model_callbacks :save
define_model_callbacks :destroy
validate :no_attachement_errors
attr_accessor :id, :import_file_file_name, :import_file_file_size, :import_file_content_type, :import_file_updated_at
has_attached_file :import_file,
:path => ":rails_root/public/system/:attachment/:id/:style/:filename",
:url => "/system/:attachment/:id/:style/:filename"
def initialize(args = { })
args.each_pair do |k, v|
self.send("#{k}=", v)
end
#id = self.class.next_id
end
def update_attributes(args = { })
args.each_pair do |k, v|
self.send("#{k}=", v)
end
end
def save
run_callbacks :save do
end
end
def destroy
run_callbacks :destroy do
end
end
# Needed for using form_for Importer::new(), :url => ..... do
def to_key
[:importer]
end
# Need a differentiating id for each new Importer.
def self.next_id
##id_counter += 1
end
# Initialize beginning id to something mildly unique.
##id_counter = Time.now.to_i
end
A form for a file upload may look like the following:
<%= form_for Importer.new, :url => import_nuts_path do |form| %>
<%= form.file_field 'import_file' %>
<%= form.submit 'Upload and Import' %>
<% end %>
and the NutsController would have the following action:
class NutsController < ActionController::Base
def import
importer = Importer.new(params[:importer])
importer.save
end
end
Note:: You have to call "save" or nothing will happen. Calling "destroy" will delete the file on the server side. Since this class is not persistent, you probably should do that after you are done with it in the controller, or you'll end up with a file space leak, if you don't do any clean up.
Security Note: This "solution" is not using any ActiveModel::MassAssignmentSecurity for new() and update_attributes(), but this class doesn't really need it. Your mileage may vary. Be careful out there!
Cheers,
-Dr. Polar
I try to use the code below to extend active record to have my order class a bit cleaner. It is however not working.
class ActiveRecord::Base
def self.has_statuses(*status_names)
validates :status,
:presence => true,
:inclusion => { :in => status_names}
status_names.each do |status_name|
scope "all_#{status_name}", where(:status => status_name)
end
status_names.each do |status_name|
define_method "#{status_name}?" do
status == status_name
end
end
end
end
In environment.rb I require the above extension with
require "#{Rails.root}/lib/active_record_extensions.rb"
If I on the other hand creates an abstract base class OrderBase and let's my order class inherit from that instead it is working as anticipated. What do I need to do to make my monkey patch "take"?
EDIT: forgot to mention I get a message saying method missing for has_statuses.
EDIT2: The above code works in console (rails c) but it does not work in test
EDIT3: I changed environment/test.rb to config.cache_classes = false and that seems to do the trick. Obviously have a lot to learn :)
Where do you store that extension code? I've found that extensions to important Rails classes don't work from the "config/initializers/" directory (where I'd prefer to keep them, for tidiness's sake), but do work if I tack the extension code on at the end of "config/environment.rb" (which I don't like at all, 'cause it gets messy really fast). I've never figured out why.
I'm on Rails 2.3.8, so if you're on Rails 3, this might no longer apply...
EDIT:
I forgot the other trick it took to get an ActiveRecord::Base extension working - the methods (class methods, at least - I haven't done it with an instance method) have to be declared in the metaclass (I think that's what it's called...). Here's my working ActiveRecord::Base extension:
ActiveRecord::Base.class_eval do
class << self
def find_with_benchmark(*args)
bg = Time.now
ret = find_without_benchmark(*args)
MyLogger.ar_time += Time.now - bg
MyLogger.ar_count += 1
return ret
end
alias_method_chain :find, :benchmark
end
end
I added the require to the top of my order class
require "#{Rails.root}/lib/active_record_extensions.rb"
class Order < ActiveRecord::Base
I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.
I have code similar to:
number_to_currency(line_item.price, :unit => "£")
littering my views in various models. Since my application deals only in GBP (£), should I not move this into each of my models so that line_item.price returns the string as it should be (i.e. number_to_currency(line_item.price, :unit => "£") and line_item.price are the same. I'm thinking that to do this I should:
def price
number_to_currency(self.price, :unit => "£")
end
but this doesn't work. If price is already defined in the model, then Rails reports 'stack level too deep', when I change def price to def amount, then it complains that number_to_currency is not defined?
If you want to change the default for your whole application, you can edit config/locales/en.yml
Mine looks like this:
# Sample localization file for English. Add more files in this directory for other locales.
# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
"en":
number:
currency:
format:
format: "%u%n"
unit: "£"
# These three are to override number.format and are optional
separator: "."
delimiter: ","
precision: 2
Everything except the unit is optional and will fall back to the default, but I put it in so I know what values I can change. you could also use the £ sign instead of £.
number_to_currency is a view helper, so it is not available in models.
You could save some key strokes by defining your own helper in application_helper.rb (so it is available to all views). Eg
def quid(price)
number_to_currency(price, :unit => "£")
end
Then call it in views:
quid(line_item.price)
The reason for the stack level too deep error is that when you say self.price in the price method you are creating an infinite recursive call to your price method as you have now overridden the normal accessor method. To avoid this you would need to access the value of the price field using the attributes hash. e.g. something like:
def price
number_to_currency(attributes['price'], :unit => "£")
end
except for the fact that number_to_currency is not available in model code for the reason Larry K describes.
Here was my approach to this problem ..
# /RAILS_ROOT/lib/app_name/currency_helper.rb
module AppName
module CurrencyHelper
include ActionView::Helpers::NumberHelper
def number_to_currency_with_pound(amount, options = {})
options.reverse_merge!({ :unit => '£' })
number_to_currency_without_pound(amount, options)
end
alias_method_chain :number_to_currency, :pound
end
end
in your models you can do this (and you won't be polluting your model with methods you aren't going to use)
class Album < ActiveRecord::Base
include AppName::CurrencyHelper
def price
currency_to_number(amount)
end
end
then for your views to all be updated include the module in one of your app helpers
module ApplicationHelper
# change default currency formatting to pounds..
include AppName::CurrencyHelper
end
Now everywhere you use the number to currency helper it will be formatted with a pound symbol, but you also have all the flexiblity of the original rails method so you can pass in the options as you did before ..
number_to_currency(amount, :unit => '$')
will convert it back to a dollar symbol.
The other answer regarding making another helper method quid(price) to simplify the repetition is probably the best approach.. however.. if you REALLY want to access view helpers in the model you can do something like:
# /RAILS_ROOT/lib/your_namespace/helper.rb
#
# Need to access helpers in the model?
# YourNamespace::Helper.instance.helper_method_name
module YourNamespace
class Helper
include Singleton
include ActionView::Helpers
end
end
then you should be able to do this in the model class:
def price
helper = YourNamespace::Helper.instance
helper.number_to_currency(read_attribute('price'), :unit => "£")
end
As of Rails 3
As Larry K describes but with this edit:
def quid(price)
number_to_currency(price, :unit => "£")
end