I am using active admin with the money - https://github.com/RubyMoney/money gem. I have some attributes handled by the money gem.
The money gem stores values in cents. When i create an entry with active admin, the correct value is created in DB (5000 for 50.00).
However when i edit an entry, the value is multiplied by 100, meaning that AA display 5000 for an original input of 50.00. If i edit anything with a money attribute, it will multiply by 100. At creation, the value goes through money logic, but at edition, somehow active admin skip that part displaying cents instead of the final monetary value.
Is there a way to use the money gem with active admin?
example :
form :html => { :enctype => "multipart/form-data"} do |f|
f.inputs "Products" do
......
f.has_many :pricings do |p|
p.input :price
p.input :_destroy, :as => :boolean,:label=>"Effacer"
end
f.actions :publish
end
Model :
# encoding: utf-8
class Pricing < ActiveRecord::Base
belongs_to :priceable, :polymorphic => true
attr_accessible :price
composed_of :price,
:class_name => "Money",
:mapping => [%w(price cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
end
Rails callbacks are very handy for creating a solution to this kind of issue.
I would just use and after_update callback.
Example:
# encoding: utf-8
class Pricing < ActiveRecord::Base
after_update :fix_price
belongs_to :priceable, :polymorphic => true
attr_accessible :price
composed_of :price,
:class_name => "Money",
:mapping => [%w(price cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
def fix_price
self.price = (self.price/100)
end
end
My problem came from my usage of Money :
composed_of :price,
:class_name => "Money",
:mapping => [%w(price_cents cents), %w(currency currency_as_string)],
:constructor => Proc.new { |price_cents, currency| Money.new(price_cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
I renamed my price in DB by price_cents, and i put it in the class where it was needed in the money declaration. I was using cent where i should have used price, and even then having the money object and the field in DB using the same name doesn't seem to work. In the end the problem was not related to Active Admin.
Related
Ok so I am building an eBay clone of sorts and I am trying to implement Braintree as the payments processor and I am having serious issues with my orders controller. All of my logic for creating customers in braintree etc. works. The issue I am having is that I can't get the product id to save when creating my orders.
I have nested the routes for my orders resource below my products resource like so:
resources :products do
resources :orders
end
Due to the routing I have set up, the parameters that are passed are:
{"_method"=>"get", "authenticity_token"=>"", "controller"=>"orders", "action"=>"new", **"product_id"=>"4"}**
My product ID gets passed into the new action. My logic is that I can store it in an instance variable and then save it to my order. My controller is as follows:
def new
if current_user.is_customer?
#client_token = Braintree::ClientToken.generate(:customer_id => current_user.id)
else
#client_token = Braintree::ClientToken.generate()
end
console
end
def create
amount = 50 # only set to a whole decimal number with no '.'
amount_str = "#{amount}.00"
#product=Product.find_by(params[:product_id])
nonce = params[:payment_method_nonce]
render action: :new and return unless nonce
#order = Order.new
#order.amount = amount
#order.user_id = current_user.id
#order.product_id= #product.id
if current_user.is_customer?
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer_id => current_user.id,
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
},
)
if result.success?
#order.save!
redirect_to support_product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
else
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer => {
:id => current_user.id,
:first_name => current_user.first_name,
:last_name => current_user.last_name
},
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
}
)
if result.success?
#order.save!
redirect_to product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
end
end
I have an association for orders and products and I have done a references migration like so:
class AddProductsUsersToOrders < ActiveRecord::Migration
def change
add_reference :orders, :product, index: true
add_foreign_key :orders, :products
add_reference :orders, :user, index: true
add_foreign_key :orders, :users
end
end
Order Model:
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
So the logic works and gets created however it saves the product_id as the first product in my database and redirects to it regardless of which product I try to order. I have spent hours on this any help is appreciated.
Long time reader of Stackoverflow but have never found myself in a position to ask a question (that hasn't already been answered). I guess there's a first time for everything so here it goes...
System Info:
Ruby Version = 1.8.7
Rails Version = 3.2.2
Situation:
We have an application with a user registration system in place. In order to hook up and populate all of our tables correctly, we are utilizing Complex/Nested Forms within the registration view. I actually have the nested forms working perfectly, everything is being populated as it should, its awesome really.
Here is the problem: I need to set one of the value of one of the nested attributes AFTER the form post but BEFORE the records are saved.
Here is a quick example so you can see what I'm talking about a little bit better:
A user registers with our site. When they register a record is created in the Users data table. Each user is also classified as a team_mate (join table) and assigned to their very own individual team (at first). But, a 'team' (table) also has an 'alias' field in it which, on the initial creation of the user we would like to set to the users first name (without having to have them enter their first name into an 'alias' field on the form).
So, I guess the question would be: How to I manually set the value of a nested attribute after the form post and before the records are saved to the database?
A (simplistic) example of the table schema looks is as follows:
Users (id, first_name, last_name, created_at, updated_at)
Team_mates(id, user_id, team_id, created_at, updated_at) - join table
Teams(id, alias, created_at, updated_at)
Models:
User.rb
class User < ActiveRecord::Base
has_many :team_mates, :dependent => :destroy
has_many :teams, :through => :team_mates, :foreign_key => :team_id
accepts_nested_attributes_for :team_mates, :allow_destroy => true
before_save :set_defaults
private
def set_defaults
#want to set :users => :team_mates_attributes => :team_attributes => :alias to #user.first_name here
# Would prefer to handle this here instead of in the controller.
end
end
Team.rb
class Team < ActiveRecord::Base
has_many :team_mates, :dependent => :destroy
has_many :users, :through => :team_mates, :foreign_key => :user_id
end
Team_mate.rb
class TeamMate < ActiveRecord::Base
belongs_to :user
belongs_to :team
accepts_nested_attributes_for :team, :allow_destroy => true
end
Controller
Users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.emails.build(:is_default_email => 1)
#user.build_login
#user.team_mates.build.build_team(:alias => 'Clinton444', :created_at => Time.new, :updated_at => Time.new)
respond_to do |format|
format.html
format.json { render :json => #match }
end
end
def create
#user = User.new(params[:user])
#user.attributes = ({ "user" => { "team_mates" => { "team" => { "alias" => #user.first_name } } } }) #--this doesn't work...
#user.attributes = ({ :user => { :team_mates => { :team => { :alias => #user.first_name } } } }) #--neither does this...
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.json { render :json => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.json { render :json => #user.errors, :status => :unprocessable_entity }
end
end
end
View
new.html.haml
= form_for(#user, :html => {:class => 'form-horizontal'}) do |f|
- if #user.errors.any?
.alert
%h2
= pluralize(#user.errors.count, "error")
prohibited this post from being saved:
%ul
- #user.errors.full_messages.each do |msg|
%li
= msg
%fieldset
.control-group
= f.label :first_name, :class => "control-label"
.controls
=f.text_field :first_name, :class => "span8"
.control-group
= f.label :last_name, :class => "control-label"
.controls
=f.text_field :last_name, :class => "span8"
= f.fields_for :emails do |e|
=e.hidden_field :is_default_email, :class => "span8"
.control-group
= e.label :email, :class => "control-label"
.controls
=e.text_field :email, :class => "span8"
= f.fields_for :team_mates do |tm|
= tm.fields_for :team do |t|
=t.hidden_field :alias, :class => "span8"
=t.hidden_field :created_at, :class => "span8"
=t.hidden_field :updated_at, :class => "span8"
= f.fields_for :login do |e|
.control-group
= e.label :user_login, :class => "control-label"
.controls
=e.text_field :user_login, :class => "span8"
.control-group
= e.label :password_encrypted, :class => "control-label"
.controls
=e.text_field :password_encrypted, :class => "span8"
.control-group
.controls
=f.submit :class => 'btn btn-primary btn-medium'
And finally
Rails server output on form post
Parameters: {"user"=>{"team_mates_attributes"=>{"0"=>{"team_attributes"=>{"created_at"=>"Wed Jun 06 09:52:19 -0600 2012", "alias"=>"asfs444", "updated_at"=>"Wed Jun 06 09:52:19 -0600 2012"}}}, "first_name"=>"lkjlkjlsdfslkjeowir", "last_name"=>"ouisodifuoixv", "emails_attributes"=>{"0"=>{"is_default_email"=>"1", "email"=>"lpisfsopf#psflsjdk.com"}}, "login_attributes"=>{"user_login"=>"lkjsdfooiusfd", "password_encrypted"=>"[FILTERED]"}}, "utf8"=>"✓", "commit"=>"Create User", "authenticity_token"=>"CQLQ93/0VlncSzMlmtLPHgaVrrvjuHFN+lN6CYCsiR8="}
After looking at the models you might be wondering where emails/logins are coming from. They're built within the model on our system, but are not really part of this question so I omitted the code for them. They are working, so the problem isn't on that side.
Check http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
To support both the creation of new objects and the editing of
existing ones we have to use an array of hashes for one-to-many
associations or a single hash for one-to-one associations. If no :id
property exists then it is assumed to represent a nested model to
create.
Not 100% sure.. I haven't used\tested it before, but this should give you an idea
#user.teams.each do |team|
team.team_mates do |team_mate|
# To edit existing
team_mate.team_attributes = [ { :id => team.id, :alias => #user.first_name } ]
# To create new
team_mate.team_attributes = [ { :alias => #user.first_name } ]
team_mate.save
end
end
I'm having some trouble with lazy loading, i'm pretty sure of it ... maybe you could point out to me where I've failed.
def setup_guild
if params[:guild]
#guild = Guild.where(:short_name => params[:guild].upcase).first
if #guild.nil?
puts "no guild with short name #{params[:guild]} found"
redirect_to root_path
else
#title = t "layout.guild_title", :guild_name => (#guild.name).capitalize
end
else
#guild = nil
end
end
Which is called in ApplicationController as a before filter.
At first I used Guild.find_with_short_name, but I had the same dumb answer as now ... that is :
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
Which is, you'd guess the #title line up there.
The thing is, if I try something similar in the console I get the expected result
ruby-1.9.2-p0 > guild = Guild.where(:short_name => "ICPT").first
Guild Load (0.5ms) SELECT "guilds".* FROM "guilds" WHERE "guilds"."short_name" = 'ICPT' LIMIT 1
=> #<Guild id: 2, name: "Inception", detail: "Inception Guild", game_server_id: 2, created_at: "2011-10-30 17:41:19", updated_at: "2011-10-30 17:41:19", short_name: "ICPT">
ruby-1.9.2-p0 > guild.name.capitalize
=> "Inception"
More, if I put something like "puts #guild.inspect" right after the fetch, the capitalization works fine, hence I think it's lazy loading failure.
I'd be happy to have some idea as to how to solve that dumb problem ... I don't really want to have an #guild.inspect for nothing in my code, i find that to be lame solution ...
Thanks !
#PanayotisMatsinopoulos As requested, here is the table Guild :
create_table "guilds", :force => true do |t|
t.string "name"
t.text "detail"
t.integer "game_server_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "short_name"
end
#PanayotisMatsinopoulos Here you go my friend ;) I still have to i18n it
#encoding: utf-8
class Guild < ActiveRecord::Base
belongs_to :game_server
has_one :game, :through => :game_server
has_many :announcement, :dependent => :destroy
validates_presence_of :name, :on => :create, :message => "dois avoir un nom"
validates_presence_of :detail, :on => :create, :message => "dois avoir une description"
validates_presence_of :game, :on => :create, :message => "dois appartenir a un jeu"
validates_presence_of :short_name, :on => :create, :message => "dois avoir un acronyme"
validates_uniqueness_of :short_name, :on => :create, :message => "est deja utilise"
validates_length_of :short_name, :within => 3..5, :on => :create, :message => "dois faire entre 3 et 5 caracteres"
validates_exclusion_of :short_name, :in => %w( users admin guilds events loots sessions characters games password), :on => :create, :message => "ne peux pas utilisé se genre d'acronyme"
validates_uniqueness_of :name, :on => :create, :message => "est deja utilise"
has_many :guild_mates, :dependent => :destroy
has_many :guild_ranks, :dependent => :destroy
has_many :guild_settings, :dependent => :destroy
has_many :events, :dependent => :destroy
has_many :characters, :dependent => :destroy
before_validation :short_name_check ,:on => :create
after_create :guild_basic_settings
def guild_basic_settings
GuildSettingType.all.each do |grst|
grs = GuildSetting.create do |g|
g.guild_id = self.id
g.guild_setting_type_id = grst.id
g.value = "false"
end
end
set_setting(["setting_allow_basic_access_for_public","setting_allow_application",
"setting_allow_event_read_for_public","setting_allow_announcement_read_for_public"],"true")
end
def set_setting(setting,value)
if setting.class == Array
setting.uniq!
setting.each do |ar|
set_setting(ar,value)
end
else
grs = nil
if setting.class == String
grs = guild_settings.includes(:guild_setting_type).where(:guild_setting_type => {:name => setting}).first
return if grs.nil?
else
grs = guild_rank_settings.where(:guild_setting_type => setting)
return if grs.nil?
end
grs.value = value
grs.save
end
end
def short_name_check
short_name.upcase! if short_name
end
def full_name
"#{name.capitalize} - #{game_server.name}"
end
def characters_for_user(user)
characters.where(:user_id => user.id)
end
def method_missing(method,*args)
check = method.to_s.split("_")
if(args.count == 0)
if check[0] == "setting"
grs = guild_settings.joins(:guild_setting_type).where(:guild_setting => { :guild_setting_types => {:name => method.to_s}}).first
unless grs.nil?
return grs.value == "true" ? true : false
else
raise "Guild > Method Missing > unknown setting : #{method.to_s}"
end
end
end
end
end
Edit : I've just seen that i didn't super method missing ... might that be the problem ?
Okay it seems that the problem was my method_missing implementation. It was lacking super call ... Now that it has been restored, everything works fine. no wonder.
Thanks #PanayotisMatsinopoulos for your help :) (also thanks a good night of sleep ;p )
you should check if name is nil also:
if #guild.nil? || #guild.name.nil?
True. method_missing should call super at the end. But, I am not convinced that your problem is there. It may be. It may not.
On the other hand, let me tell something that I believe has more chances to be your problem. This is the fact that you carry out your validation on presence of name only :on => :create. This means that an update of an object Guild that does not contain the name will pass validation and will be saved in the database without problem. Then your setup_guild will definitely throw the error:
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
i.e. the error this discussion started about.
Hence, my suggestion is to remove your :on => :create condition on the validation of name. (an BTW...I suggest that you remove it from all your validations unless you know what you are doing)
But then, I cannot prove that this was your problem in the first place. I am just putting here my advice, rather than my positive answer as a solution to your problem.
I have a form with a date select.
I have the following form:
<%= date_select :user, :birthday, {:start_year => Time.now.year, :end_year => 1910,:order => [:day,:month,:year], :prompt => { :day => 'day', :month => 'month', :year => 'year' }}, { :class => "default" } %>
Validation in my model:
validates :birthday, :if => :should_validate_birthday?,
:presence => {:message => "Please enter your friend's birthdate"},
:date => { :after => Date.civil(1910,1,1), :before => Date.today, :message => "Please enter a valid date"}
Here is an example of what the user submits in the log:
"user"=>{"name"=>"rewrwe", "birthday(3i)"=>"1", "birthday(2i)"=>"", "birthday(1i)"=>"2008", "email"=>""}}
NOTE that the value for the month is blank.
IN the controller I create the user
create
#user = User.new(params[:user])
If #user.save
#go to the confirm page
end
end
No error messages are shown even though the month is missing because for some reason when I try to save the model it converts an empty month to "January" or 01.
This is very frustrating as I don't want users to submit bad data by accident.
How can I stop Rails from doing this and make sure all the date information is submitted?
Separate the three fields with attr_accessor:
class User < ActiveRecord::Base
attr_accessor :birthday_year, :birthday_month, :birthday_date
validates_presence_of :birthday_year, :birthday_month, :birthday_date
# ...
end
And then in your view just have different selects for each of them. That's all the date_select does but it splits them up in the view, rather than in the model, which makes it hard to validate each field.
I am using the awesome_nested_set to do a simple drag and drop reordering of news items and the post happens but the position field in my DB is not updated...
Here is my model:
class NewsItem < ActiveRecord::Base
translates :title, :body, :external_url
attr_accessor :locale, :position # to hold temporarily
alias_attribute :content, :body
validates :title, :content, :publish_date, :presence => true
has_friendly_id :title, :use_slug => true
acts_as_indexed :fields => [:title, :body]
acts_as_nested_set
default_scope :order => "publish_date DESC"
# If you're using a named scope that includes a changing variable you need to wrap it in a lambda
# This avoids the query being cached thus becoming unaffected by changes (i.e. Time.now is constant)
scope :not_expired, lambda {
news_items = Arel::Table.new(NewsItem.table_name)
where(news_items[:expiration_date].eq(nil).or(news_items[:expiration_date].gt(Time.now)))
}
scope :published, lambda {
not_expired.where("publish_date < ?", Time.now)
}
scope :latest, lambda { |*l_params|
published.limit( l_params.first || 10)
}
# rejects any page that has not been translated to the current locale.
scope :translated, lambda {
pages = Arel::Table.new(NewsItem.table_name)
translations = Arel::Table.new(NewsItem.translations_table_name)
includes(:translations).where(
translations[:locale].eq(Globalize.locale)).where(pages[:id].eq(translations[:news_item_id]))
}
def not_published? # has the published date not yet arrived?
publish_date > Time.now
end
# for will_paginate
def self.per_page
20
end
end
Anyone know why this wouldn't work?