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(&macro).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.

Resources