Parse ruby code - ruby-on-rails
I need help in one problem. I have a table with columns that contain some ruby code, like this: self.org_premium = self.volume / 12 * 0.1492 self.billing_premium = self.subscriber_premium + self.org_premium or employment_level == 'P' or vol_life.save.
And now I want find methods in these strings, but some Rails methods, like save or nil? must be ignored.
I used Ripper, but his method slice return only 1 param.
Maybe you have some idea about this?
When you use Ripper to slice, e.g.:
$ irb
2.0.0p247 :001 > p Ripper.slice('def m(a) nil end', 'ident')
To see what events are available, just evaluate the constants it refers to in the doc: EVENTS which are further broken down into PARSER_EVENTS, and SCANNER_EVENTS.
$ irb
2.0.0p247 :001 > require 'ripper'
2.0.0p247 :002 > Ripper::EVENTS
=> [:BEGIN, :END, :alias, :alias_error, :aref, :aref_field, :arg_ambiguous, :arg_paren, :args_add, :args_add_block, :args_add_star, :args_new, :array, :assign, :assign_error, :assoc_new, :assoc_splat, :assoclist_from_args, :bare_assoc_hash, :begin, :binary, :block_var, :block_var_add_block, :block_var_add_star, :blockarg, :bodystmt, :brace_block, :break, :call, :case, :class, :class_name_error, :command, :command_call, :const_path_field, :const_path_ref, :const_ref, :def, :defined, :defs, :do_block, :dot2, :dot3, :dyna_symbol, :else, :elsif, :ensure, :excessed_comma, :fcall, :field, :for, :hash, :if, :if_mod, :ifop, :lambda, :magic_comment, :massign, :method_add_arg, :method_add_block, :mlhs_add, :mlhs_add_star, :mlhs_new, :mlhs_paren, :module, :mrhs_add, :mrhs_add_star, :mrhs_new, :mrhs_new_from_args, :next, :opassign, :operator_ambiguous, :param_error, :params, :paren, :parse_error, :program, :qsymbols_add, :qsymbols_new, :qwords_add, :qwords_new, :redo, :regexp_add, :regexp_literal, :regexp_new, :rescue, :rescue_mod, :rest_param, :retry, :return, :return0, :sclass, :stmts_add, :stmts_new, :string_add, :string_concat, :string_content, :string_dvar, :string_embexpr, :string_literal, :super, :symbol, :symbol_literal, :symbols_add, :symbols_new, :top_const_field, :top_const_ref, :unary, :undef, :unless, :unless_mod, :until, :until_mod, :var_alias, :var_field, :var_ref, :vcall, :void_stmt, :when, :while, :while_mod, :word_add, :word_new, :words_add, :words_new, :xstring_add, :xstring_literal, :xstring_new, :yield, :yield0, :zsuper, :CHAR, :__end__, :backref, :backtick, :comma, :comment, :const, :cvar, :embdoc, :embdoc_beg, :embdoc_end, :embexpr_beg, :embexpr_end, :embvar, :float, :gvar, :heredoc_beg, :heredoc_end, :ident, :ignored_nl, :int, :ivar, :kw, :label, :lbrace, :lbracket, :lparen, :nl, :op, :period, :qsymbols_beg, :qwords_beg, :rbrace, :rbracket, :regexp_beg, :regexp_end, :rparen, :semicolon, :sp, :symbeg, :symbols_beg, :tlambda, :tlambeg, :tstring_beg, :tstring_content, :tstring_end, :words_beg, :words_sep]
2.0.0p247 :009 > Ripper::PARSER_EVENTS
=> [:BEGIN, :END, :alias, :alias_error, :aref, :aref_field, :arg_ambiguous, :arg_paren, :args_add, :args_add_block, :args_add_star, :args_new, :array, :assign, :assign_error, :assoc_new, :assoc_splat, :assoclist_from_args, :bare_assoc_hash, :begin, :binary, :block_var, :block_var_add_block, :block_var_add_star, :blockarg, :bodystmt, :brace_block, :break, :call, :case, :class, :class_name_error, :command, :command_call, :const_path_field, :const_path_ref, :const_ref, :def, :defined, :defs, :do_block, :dot2, :dot3, :dyna_symbol, :else, :elsif, :ensure, :excessed_comma, :fcall, :field, :for, :hash, :if, :if_mod, :ifop, :lambda, :magic_comment, :massign, :method_add_arg, :method_add_block, :mlhs_add, :mlhs_add_star, :mlhs_new, :mlhs_paren, :module, :mrhs_add, :mrhs_add_star, :mrhs_new, :mrhs_new_from_args, :next, :opassign, :operator_ambiguous, :param_error, :params, :paren, :parse_error, :program, :qsymbols_add, :qsymbols_new, :qwords_add, :qwords_new, :redo, :regexp_add, :regexp_literal, :regexp_new, :rescue, :rescue_mod, :rest_param, :retry, :return, :return0, :sclass, :stmts_add, :stmts_new, :string_add, :string_concat, :string_content, :string_dvar, :string_embexpr, :string_literal, :super, :symbol, :symbol_literal, :symbols_add, :symbols_new, :top_const_field, :top_const_ref, :unary, :undef, :unless, :unless_mod, :until, :until_mod, :var_alias, :var_field, :var_ref, :vcall, :void_stmt, :when, :while, :while_mod, :word_add, :word_new, :words_add, :words_new, :xstring_add, :xstring_literal, :xstring_new, :yield, :yield0, :zsuper]
2.0.0p247 :010 > Ripper::SCANNER_EVENTS
=> [:CHAR, :__end__, :backref, :backtick, :comma, :comment, :const, :cvar, :embdoc, :embdoc_beg, :embdoc_end, :embexpr_beg, :embexpr_end, :embvar, :float, :gvar, :heredoc_beg, :heredoc_end, :ident, :ignored_nl, :int, :ivar, :kw, :label, :lbrace, :lbracket, :lparen, :nl, :op, :period, :qsymbols_beg, :qwords_beg, :rbrace, :rbracket, :regexp_beg, :regexp_end, :rparen, :semicolon, :sp, :symbeg, :symbols_beg, :tlambda, :tlambeg, :tstring_beg, :tstring_content, :tstring_end, :words_beg, :words_sep]
The 'ident' is an event for method name definition in this case, and that event isn't really equivalent to a method that is called in the code.
I'm not sure that Ripper would be the easiest way to parse out method names that are used. In addition, the ability for Ruby to handle calls handled by method_missing really make it difficult to see what could be interpreted.
Like I said in the comments, there are several other ways to parse methods you might look into.
You could even just make something similar with string operations/checking available methods, e.g.
class A
IGNORE = %w{save nil?}
def find_possible_methods(s)
s.split(/[\-\ ,\.\(\)\{\}\[\]]/).reject{|c| c =~ /[0-9\*\-\/\+\%\=\~].*/ || c.empty? || IGNORE.include?(c)}
end
def find_implemented_methods(s)
(s.split(/[\-\ ,\.\(\)\{\}\[\]]/) & (methods + private_methods).collect(&:to_s)).reject{|c| IGNORE.include?(c)}
end
end
Usage:
a = A.new
=> #<A:0x007facb9a94be8>
a.find_possible_methods 'self.org_premium = self.volume / 12 * 0.1492 self.billing_premium = self.subscriber_premium + self.org_premium'
=> ["self", "org_premium", "self", "volume", "self", "billing_premium", "self", "subscriber_premium", "self", "org_premium"]
Related
Getting error when trying to encode message with Protobuf
Protobuf generated: # source: event.proto require 'google/protobuf' Google::Protobuf::DescriptorPool.generated_pool.build do add_file("event.proto", :syntax => :proto3) do add_message "myapp.Event" do optional :name, :string, 1 optional :entity, :enum, 2, "myapp.Event.Entity" oneof :event_data do optional :first_event_data, :message, 3, "myapp.Event.FirstEventData" optional :second_event_data, :message, 4, "myapp.Event.SecondEventData" end end add_message "myapp.Event.FirstEventData" do optional :id, :string, 1 optional :to, :string, 2 optional :from, :string, 3 end add_message "myapp.Event.SecondEventData" do optional :metadata_url, :string, 1 end add_enum "myapp.Event.Entity" do value :FIRST, 0 value :SECOND, 1 end end end module Myapp Event = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event").msgclass Event::FirstEventData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.FirstEventData").msgclass Event::SecondEventData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.SecondEventData").msgclass Event::Entity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.Entity").enummodule end Now in the console when I do message = Myapp::Event.new( entity: :SECOND, name: "started", event_data: { second_event_data: Myapp::Event::SecondEventData.new( metadata_url: "local-dev-url", ) } ) I get this error: Traceback (most recent call last): 3: from (irb):178 2: from (irb):178:in `new' 1: from (irb):178:in `initialize' ArgumentError (Unknown field name 'event_data' in initialization map entry.) I have tried different combinations and every time different error, I think I am organising my message incorrectly. Any help would be appreciated greatly. Thanks
Well, after lots of research, apparently I can't do it all at once. I need to create 2 or 3 seperate objects: i.e message = Myapp::Event.new( entity: :SECOND, name: "started") And then I can do this: message.second_event_data = Myapp::Event::SecondEventData.new( metadata_url: "local-dev-url", ) Then when I encode it, second_event_data will sits under the event_data
How remove an extra space in string and convert to int type?
When I enter a price more than three digits, such as 1000, the js script makes the 1 000, 10 000, etc. It puts a space for the user's convenience. Validation display is not a numeric type. How convert to int type in this situation? validates :price, numericality: { only_integer: true, allow_nil: true }
here is a slightly better solution that would still allow you to have nil values (please note that nil.to_i is 0 as well as any "text".to_i is also 0) def my_method(str) Integer(str.gsub(/\s/, '')) rescue ArgumentError => e nil end Example of use: my_method('1000') => 1000 my_method('1 000') => 1000 my_method(' ') => nil my_method('some_text') => nil my_method('1.000') => nil my_method('1,000') => nil If you want to treat . and , you can adapt the regex in gsub.
I found a solution: 2.3.0 :004 > "1 000".delete(' ') => "1000" 2.3.0 :005 > "1 000".delete(' ').to_i => 1000
How can I validate an attribute to be between various noncontinuous ranges?
I'm wanting to validate that my height attribute is within a bunch of different ranges. So my attempt was something like what I did below... however this is incorrect. How should this be done? Thanks! validates :height, :numericality => { in: { 5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118, 6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118, 7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118 } }
You can put that in a custom validate method: class YourModel < ActiveRecord::Base VALID_HEIGHT_RANGES = [5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118, 6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118, 7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118] validate :height_in_valid_range private def height_in_valid_range VALID_HEIGHT_RANGES.each do |range| unless range.include? height errors.add :height, "not in valid range" break end end end end
object existence not found in console but found when called in view?
In my application I have a user model with a has many relationship to a status_updates model. Currently, my database only has 1 user and zero status_updates saved. What is bizarre is that when I search for status_updates or for a relationship between these objects in consol, affirms that are nil status_updates in the database. StatusUpdate.count (0.2ms) SELECT COUNT(*) FROM "status_updates" => 0 StatusUpdate.any? (0.1ms) SELECT COUNT(*) FROM "status_updates" => false user.status_updates.count (0.2ms) SELECT COUNT(*) FROM "status_updates" WHERE "status_updates"."user_id" = 1 => 0 user.status_updates.any? (0.2ms) SELECT COUNT(*) FROM "status_updates" WHERE "status_updates"."user_id" = 1 => false So, that's clear for me. BUT, when in my application I write the following, a status_update object is returned! def end_date if self.status_updates.any? == false return self.status_updates.any? elsif self.status_updates.any? == true return self.status_updates.first end end This is the call in my view current_user.end_date And this is what is returned to the view: #<StatusUpdate:0x007fa99765d6f8> And if I change the call in the view to this: current_user.status_updates.first => <StatusUpdate:0x007fa99740b5f8> But, if I call this: current_user.status_updates.count => 0 current_user.status_updates.any? => true In the view when I just use current_user.status_updates it returns [#<StatusUpdate id: nil, created_at: nil, updated_at: nil, user_id: 1, current_weight: 0.0, current_bf_pct: 0.0, current_lbm: 0.0, current_fat_weight: 0.0, change_in_weight: nil, change_in_bf_pct: nil, change_in_lbm: nil, change_in_fat_weight: nil, total_weight_change: nil, total_bf_pct_change: nil, total_lbm_change: nil, total_fat_change: nil>] What's going on here?! User Model relationship has_many :status_updates, dependent: :destroy Status Update Model Relationship belongs_to :user Status Update Model class StatusUpdate < ActiveRecord::Base belongs_to :user after_initialize :default_values before_save :sanitize attr_accessible :current_weight, :current_bf_pct, :current_lbm, :current_fat_weight, :change_in_weight, :change_in_bf_pct, :change_in_lbm, :change_in_fat_weight, :total_weight_change, :total_bf_pct_change, :total_lbm_change, :total_fat_change, :created_at validates :user_id, presence: true validates :current_bf_pct, presence: true, numericality: true, length: { minimum: 2, maximum:5 } validates :current_weight, presence: true, numericality: true, length: { minimum: 2, maximum:5 } validates :current_lbm, presence: true validates :current_fat_weight, presence: true def sanitize if self.current_bf_pct >= 0.5 self.current_bf_pct /= 100 if self.current_bf_pct <= 0.04 self.current_fb_pct *= 100 end end self.current_fat_weight = self.current_weight * self.current_bf_pct self.current_lbm = self.current_weight - self.current_fat_weight end def default_values if self.created_at == nil self.current_bf_pct = 0.20 self.current_weight = 0 self.current_lbm = 0 self.current_fat_weight = 0 self.change_in_weight = 0 self.change_in_bf_pct = 0 self.change_in_lbm = 0 self.change_in_fat_weight = 0 self.total_weight_change = 0 self.total_bf_pct_change = 0 self.total_lbm_change = 0 self.total_fat_change = 0 end end def previous_status_update previous_status_update = user.status_updates.where( "created_at < ? ", self.created_at ).first if previous_status_update == nil return self else previous_status_update end end default_scope order: 'status_updates.created_at DESC' end User Model: class User < ActiveRecord::Base # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable before_create :sanitize has_many :status_updates, dependent: :destroy has_many :meals, dependent: :destroy has_many :custom_foods, dependent: :destroy has_many :meal_foods, through: :meals # after_initialize :default_values attr_accessor :user_password, :user_password_confirmation, :current_password attr_accessible :email, :password, :password_confirmation, :current_password, :goal, :measurement, :bmr_formula, :fat_factor, :protein_factor, :remember_me, :deficit_amnt, :target_bf_pct, :activity_factor, :current_password validates :email, presence: true validates :target_bf_pct, presence: true, on: :update, length: { minimum: 3, maximum: 4 } validates :activity_factor, presence: true, on: :update validates :deficit_amnt, presence: true, on: :update validates :fat_factor, presence: true, on: :update validates :protein_factor, presence: true, on: :update def new? self.created_at <= 1.minutes.ago.to_date ? true : false end def sanitize #inputs self.activity_factor = 1.3 self.deficit_amnt = 1 self.target_bf_pct = 10 self.fat_factor = 0.45 self.protein_factor = 1 end def end_date if self.status_updates.any? == false #Time.now self.status_updates.any? elsif self.status_updates.any? == true #(self.start_date + self.weeks_to_goal.to_i.weeks).strftime("%m/%d/%Y") self.status_updates end end def start_date if self.status_updates.any? == true self.status_updates.first.created_at end end def daily_caloric_deficit self.tdee.to_d - self.daily_intake.to_d end def current_fat_weight BigDecimal(self.latest_status_update.current_fat_weight, 4) end def current_lbm BigDecimal(self.latest_status_update.current_lbm, 4) end def current_bf_pct BigDecimal(self.latest_status_update.current_bf_pct * 100, 4) end def current_weight BigDecimal(self.latest_status_update.current_weight, 4) end def total_weight self.latest_status_update.current_weight end # def lbm # self.latest_status_updates.current_lbm # end def recent_weight_change BigDecimal(self.latest_status_update.current_weight - self.latest_status_update.previous_status_update.current_weight, 2) end def recent_lbm_change BigDecimal(self.latest_status_update.current_lbm - self.latest_status_update.previous_status_update.current_lbm, 2) end def recent_fat_change BigDecimal(self.latest_status_update.current_fat_weight - self.latest_status_update.previous_status_update.current_fat_weight, 3) end def total_lbm_change BigDecimal(self.latest_status_update.current_lbm - self.oldest_status_update.current_lbm, 3) end def total_fat_change BigDecimal(self.latest_status_update.current_fat_weight - self.oldest_status_update.current_fat_weight, 3) end def total_weight_change BigDecimal(self.latest_status_update.current_weight - self.oldest_status_update.current_weight, 3) end def last_date self.status_updates.last.created_at.strftime("%m/%d/%Y") end def beginning_date self.status_updates.first.created_at.strftime("%m/%d/%Y") end def latest_status_update self.status_updates.first end def oldest_status_update self.status_updates.last end def bmr cur_lbm = self.current_lbm cur_lbm *= 0.45 '%.2f' % (370 + (21.6 * cur_lbm.to_d)) end def target_weight tar_bf_pct = self.target_bf_pct /= 100 '%.2f' % ((self.total_weight * tar_bf_pct)+ self.current_lbm) end def fat_to_burn '%.2f' % (self.total_weight.to_d - self.target_weight.to_d) end def tdee '%.2f' % (self.bmr.to_d * self.activity_factor.to_d) end def deficit_pct daily_cal_def = ((self.deficit_amnt.to_f * 3500)/7) (daily_cal_def.to_d/self.tdee.to_d) end def daily_calorie_burn '%.2f' % (self.tdee.to_d * self.deficit_pct.to_d) end def weekly_calorie_burn_rate '%.2f' % (self.daily_calorie_burn.to_d*7) end def weeks_to_goal '%.2f' % (self.fat_to_burn.to_d*3500/self.weekly_calorie_burn_rate.to_d) end def daily_intake '%.2f' % (self.tdee.to_d - self.daily_calorie_burn.to_d) end def total_grams_of(macro) self.meal_foods.map(¯o).inject(:+) end def pct_fat_satisfied #how much of a macro is needed? fat_needed = self.fat_factor * self.current_lbm #how much is in the meal? fat_provided = self.total_grams_of(:fat) #percent needed pct_fulfilled = fat_provided.to_f/fat_needed.to_f BigDecimal(pct_fulfilled, 2)*100 end def pct_protein_satisfied #how much protien is needed? protein_needed = self.protein_factor * self.current_lbm #how much protien is provided? protein_provided = total_grams_of(:protien) #pct of protien satisfied? pct_fulfilled = protein_provided.to_f/protein_needed.to_f BigDecimal(pct_fulfilled, 2)*100 end def pct_carbs_satisfied #how many carbs are needed? cals_required = self.tdee.to_f - (self.tdee.to_f * self.deficit_pct.to_f) fat_cals = total_grams_of(:fat) * 9 protien_cals = total_grams_of(:protien) * 4 #how many carbs are provided? cals_provided = fat_cals + protien_cals cals_balance = cals_required - cals_provided carbs_needed = cals_balance/4 carbs_provided = total_grams_of(:carbs) BigDecimal(carbs_provided / carbs_needed, 2) * 100 end end I've tried resetting my database and the same issue comes up. To recap: It seems that a status_update is being initialized, and this initialized version of the object is what is available to the view. All the attributes are the initialized ones, which is why it doesn't have an id, or a timestamp for when it was created... because it never was created. however when I try to access it from console I realized that it's giving me 'nil' and all the predictable values because in console it's looking for an already created and object relationship in the relationship. So the more precise question here is the view returning the initialized version of the object instead of what console returns? When the app hits a method within the user model it throws an error because the status_update doesn't exist when it calls something like self.status_updates.first
You're probably doing #status_update = user.status_updates.build in your controller - this is pretty common place so that you can then put a form on the page that allows the user to submit a new status update. This object is unsaved, so user.status_updates.count which is guaranteed to hit the database (and run select count(*) ...) doesn't count it. It is however in the in memory cache of the status updates association for that user and so user.status_updates.any? returns true. You might also note that it's extremely unidiomatic to compare the return value of predicate methods with true or false. It reads funny and methods like this may return a value with equivalent truthiness other than true or false (for example nil instead of false).
This line: #<StatusUpdate id: nil, created_at: nil, updated_at: nil, user_id: 1, current_weight: 0.0, current_bf_pct: 0.0, current_lbm: 0.0, current_fat_weight: 0.0, change_in_weight: nil, change_in_bf_pct: nil, change_in_lbm: nil, change_in_fat_weight: nil, total_weight_change: nil, total_bf_pct_change: nil, total_lbm_change: nil, total_fat_change: nil>] The id: nil indicates that this model hasn't been saved yet. Models only get an id once you save them. The fact that it has user_id: 1 indicates that you probably created this through the user, as Frederick indicated. So somewhere you're creating this instance and not saving it. PS, it's broadly considered poor ruby form to check if booleans are false or true. e.g. don't do this: self.status_updates.any? == false do self.status_updates.any? which is true or false anyway, then just use if and unless as your checks. Better still, self is assumed, so in your case, you can just do: status_updates.any? PPS active_record defines a new? method for exactly this use case. A record that has not yet been saved is new and so new? will return true. I strongly discourage you from overwriting this method, or anything else defined by active_record.
Setting single coordinates for RGeo Point
RGeo provides built in methods for POINT features, for example getter methods lat() and lon() to pull latitude and longitude values from a POINT object. Unfortunately, these don't work as setters. For example: point = RGeo::Geographic.spherical_factory(:srid => 4326).point(3,5) // => #<RGeo::Geographic::SphericalPointImpl:0x817e521c "POINT (3.0 5.0)"> I can do this: point.lat // => 5.0 point.lon // => 3.0 But I can't do: point.lat = 4 // => NoMethodError: undefined method `lat=' for #<RGeo::Geographic::SphericalPointImpl:0x00000104024770> Any suggestions as to how to implement setter methods? Would you do it in the Model or extend the Feature class?
I'm the author of RGeo, so you can consider this answer authoritative on that basis. In short, PLEASE AVOID DOING THIS. RGeo objects intentionally have no setter methods because they are meant to be immutable objects. This is so that they can be cached, used as hash keys, used across threads, etc. Some of the RGeo calculations assume that the value of a feature object will never change, so making changes like this could have unexpected and unpredictable consequences. If you really want a "changed" value, create a new object. For example: p1 = my_create_a_point() p2 = p1.factory.point(p1.lon + 20.0, p2.lat)
I have found something that works, although there might be more elegant solutions. In my Location model I have added theses methods: after_initialize :init def init self.latlon ||= Location.rgeo_factory_for_column(:latlon).point(0, 0) end def latitude self.latlon.lat end def latitude=(value) lon = self.latlon.lon self.latlon = Location.rgeo_factory_for_column(:latlon).point(lon, value) end def longitude self.latlon.lon end def longitude=(value) lat = self.latlon.lat self.latlon = Location.rgeo_factory_for_column(:latlon).point(value, lat) end
I ended up doing something like this in my model: class MyModel < ActiveRecord::Base attr_accessor :longitude, :latitude attr_accessible :longitude, :latitude validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true before_save :update_gps_location def update_gps_location if longitude.present? || latitude.present? long = longitude || self.gps_location.longitude lat = latitude || self.gps_location.latitude self.gps_location = RGeo::Geographic.spherical_factory(srid: 4326).point(long, lat) end end end Then you can just update the position like so: my_model.update_attributes(longitude: -122, latitude: 37) I didn't load up longitude/latitude in an after_initialize block because in my app we never need to read the data, only write it. You could easily add that though. Credit to this answer for the validations.