I am trying to get the latitude and longitude values for two different values. These are set in the attributes start and end in my Mileage model.
geocoded_by :end, :latitude => :latitude, :longitude => :longitude
geocoded_by :start, :latitude => :start_lat, :longitude => :start_lon
I have the following attributes in my Mileage model:
start
start_lat
start_long
end
latitude
longitude
Depending on which geocoded_by line is at the top of the Mileage model, only one of the attributes is geocoded. Is there a way of making both attributes get processed by the geocoder gem?
The short answer is no.
But its not hard to roll your own solution. Basically geocoded_by: sets up a simple way to geocode an address lat/long from a callback with a handful of options (lat/long column name, etc).
There's a utility method for looking up the lat/lng of an address:
Geocoder.coordinates(the_start_address)
=> [42.700149, -74.922767]
so you can use the geocoded_by: method to handle the end lat/long in your example and then do something like
before_save :geocode_the_start_address
def geocode_the_start_address
coords = Geocoder.coordinates(self.start)
self.start_lat = coords[0]
self.start_long = coords[1]
end
Honestly I'd name things more consistently myself -- start_address, end_address, start_lat, start_long, etc. And I'd also use a custom method for geocoding the start and end address instead of geocoded_by so the code is more consistent.
I agree with John Paul, but I'd do it in an after_save - so, basically, have your Milage model perform geocoding on the start address on creation, then after creation has taken place (e.g. the geocoding has been performed on the start address and you have a valid start address), perform the geocoding on the end address using a separate callback.
Related
I'm trying to track user login history for stat purposes but its not clear to me what the best way to go about it would be. I could have a separate table that records users and their login stats with a date, but that table could get REALLY big. I could track some historic fields in the User model/object itself in a parse-able field and just update it (them) with some delimited string format. e.g. split on :, get the last one, if an included date code isn't today, add an item (date+count) otherwise increment, then save it back. At least with this second approach it would be easy to remove old items (e.g. only keep 30 days of daily logins, or IPs), as a separate table would require a task to delete old records.
I'm a big fan of instant changes. Tasks are useful, but can complicate things for maintenance reasons.
Anyone have any suggestions? I don't have an external data caching solution up or anything yet. Any pointers are also welcome! (I've been hunting for similar questions and answers)
Thanks!
If you have the :trackable module, I found this the easiest way. In the User model (or whichever model you're authenticating)
def update_tracked_fields!(request)
old_signin = self.last_sign_in_at
super
if self.last_sign_in_at != old_signin
Audit.create :user => self, :action => "login", :ip => self.last_sign_in_ip
end
end
(Inspired by https://github.com/plataformatec/devise/wiki/How-To:-Turn-off-trackable-for-admin-users)
There is a nice way to do that through Devise.
Warden sets up a hook called after_set_user that runs after setting a user. So, supposed you have a model Login containing an ip field, a logged_in_at field and user_id field, you can only create the record using this fields.
Warden::Manager.after_set_user :except => :fetch do |record, warden, options|
Login.create!(:ip => warden.request.ip, :logged_in_at => Time.now, :user_id => record.id)
end
Building upon #user208769's answer, the core Devise::Models::Trackable#update_tracked_fields! method now calls a helper method named update_tracked_fields prior to saving. That means you can use ActiveRecord::Dirty helpers to make it a little simpler:
def update_tracked_fields(request)
super
if last_sign_in_at_changed?
Audit.create(user: self, action: 'login', ip: last_sign_in_ip)
end
end
This can be simplified even further (and be more reliable given validations) if audits is a relationship on your model:
def update_tracked_fields(request)
super
audits.build(action: 'login', ip: last_sign_in_ip) if last_sign_in_at_changed?
end
Devise supports tracking the last signed in date and the last signed in ip address with it's :trackable module. By adding this module to your user model, and then also adding the correct fields to your database, which are:
:sign_in_count, :type => Integer, :default => 0
:current_sign_in_at, :type => Time
:last_sign_in_at, :type => Time
:current_sign_in_ip, :type => String
:last_sign_in_ip, :type => String
You could then override the Devise::SessionsController and it's create action to then save the :last_sign_in_at and :last_sign_in_ip to a separate table in a before_create callback. You should then be able to keep them as long you would like.
Here's an example (scribd_analytics)
create_table 'page_views' do |t|
t.column 'user_id', :integer
t.column 'request_url', :string, :limit => 200
t.column 'session', :string, :limit => 32
t.column 'ip_address', :string, :limit => 16
t.column 'referer', :string, :limit => 200
t.column 'user_agent', :string, :limit => 200
t.column 'created_at', :timestamp
end
Add a whole bunch of indexes, depending on queries
Create a PageView on every request
We used a hand-built SQL query to take out the ActiveRecord overhead on
this
Might try MySQL's 'insert delayed´
Analytics queries are usually hand-coded SQL
Use 'explain select´ to make sure MySQL isusing the indexes you expect
Scales pretty well
BUT analytics queries expensive, can clog upmain DB server
Our solution:
use two DB servers in a master/slave setup
move all the analytics queries to the slave
http://www.scribd.com/doc/49575/Scaling-Rails-Presentation-From-Scribd-Launch
Another option to check is Gattica with Google Analytics
I hate answering my own questions, especially given that you both gave helpful answers. I think answering my question with the approach I took might help others, in combination with your answers.
I've been playing with the Impressionist Gem (the only useful page view Gem since the abandoned RailStat) with good results so far. After setting up the basic migration, I found that the expected usage follows Rail's MVC design very closely. If you add "impressionist" to a Controller, it will go looking for the Model when logging the page view to the database. You can modify this behaviour or just call impressionist yourself in your Controller (or anywhere really) if you're like me and happen to be testing it out on a Controller that doesn't have a Model.
Anyways, I got it working with Devise to track successful logins by overriding the Devise::SessionsController and just calling the impressionist method for the #current_member: (don't forget to check if it's nil! on failed login)
class TestSessionController < Devise::SessionsController
def create
if not #current_member.nil?
impressionist(#current_member)
end
super
end
end
Adding it to other site parts later for some limited analytics is easy to do. The only other thing I had to do was update my routes to use the new TestSessionController for the Devise login route:
post 'login' => 'test_session#create', :as => :member_session
Devise works like normal without having to modify Devise in anyway, and my impressionist DB table is indexed and logging logins. I'll just need a rake task later to trim it weekly or so.
Now I just need to work out how to chart daily logins without having to write a bunch of looping, dirty queries...
There is also 'paper_trail' gem, that allows to track model changes.
There are a couple of places where I could do what I need, but I'm not sure where the best place is in line with good practices.
I have an Orders controller, and after a successful order is created I want to create a subscription (but only if the order is a success), and a referral (but only if the order is associated with one).
Now the obvious choice is to use after_create on the Order model... but... how can I get session data into that? (The referral ids, friend ids and voucher ids are only in the session as there's no need to store them in the Order db).
So should I just create the Subscription and Referral objects in the create action (how I have it at the mo) or is there a better way?
Here's my create action:
(#order.purchase only returns true if the payment was successful)
def create
if #order.save
if #order.purchase
Subscription.create(:order_id => #order.id, :product_id => #order.product_id)
if #order.voucher
Referral.create(:user_id => session[:friend_id], :order_id => #order.id,
:voucher_amount => #voucher_value)
end
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
Any help would be appreciated - I really would like to do this properly so I hope no one minds me asking what is probably a simple question.
I recently had a similar question, please take a look, i think that a simple virtual attribute in a callback will do it for you as well.
Fetch current user in after_create filter
use of callbacks will make your life easy, you need to use after_save
do all your stuff in after_save callback of order model. see rails api doc for callback here
Edit: If the session variable is not available with model, you can have a post_save method to deal with all logic which also accepts all require params like
like
class Order < ActiveRecord::Base
def post_save require_attr
#create subscriptions
# create referral
end
end
I've just created two models and one "join table". Person, Adress (create_adresses_personss)
class Person < ActiveRecord::Base
has_and_belongs_to_many :streets
end
class Street < ActiveRecord::Base
has_and_belongs_to_many :persons
end
Now I want to add some data to these models in the db/seeds.rb file. The tutorial I follow just adds the objects:
person = Person.create :name => 'Dexter'
street.create[{:streetname => 'street1'},
{:streetname => 'street2'},
{:streetname => 'julianave'},
{:streetname => 'street3'}]
Question 1: Why is persons' data added differently than streets'? Is it just the tutorial that wants to show that there are many ways of adding data in the seeds.rb?
Question 2: The tutorial doesn't make the connections/joins in the seeds.rb. It does that in the rails console;
>>p1 = Person.find(1)
>>s1 = Street.find(1)
>>p1.streets << s1
Can't theese connections be made in the seeds.rb file?
Question 3: Would it be better to do this join with a "rich many_to_many-assocciation"?
Thanks for your time and patience with a beginner ;)
1) The first method is creating one object. The second method is creating multiple objects. However, for the second method you would need to do Street.create, not street.create.
2) Yes, you can do that in the seed file the same way.
3) The "Rich many-to-many" you're talking about is an association with a Join Model, I guess you're talking about. This is opposed to just a join table, which is what has_and_belongs_to_many does. To use a join model, you'll want to look up has_many :through. It's generally considered better to always use a proper join model, however I still use HABTM when I just need a quick, simple association. has_many :through allows for more options and more flexibility, but it is a little more complicated to setup (not that much, though). It's your decision.
One way that I like to create seed data for many-to-many associations is setting up one of the models, the adding a tap block that sets up the other models through the association.
Person.create!(:name => "Fubar").tap do |person|
3.times do |n|
person.streets.create!(:streetname => "street #{n}")
end
# OR
person.streets.create!([
{:streetname => "street 1"},
{:streetname => "street 2"},
... and so on
])
end
All tap is doing is executing the block with the object as it's only parameter. I find it convenient for seeds.
One other tip I would toss out there would be to have your model attribute names spaced on the words with underscores.
:street_name instead of :streetname
The difference is more profound when you start wanting to use some of the ActiveSupport helers that take model attributes and turn them into text strings for use in the UI.
e
:streetname.to_s.titleize # "Streetname"
:street_name.to_s.titleize # "Street Name"
And one last nitpick, you might want your join table to be addresses_people not addresses_persons since the rais inflector is going to pluralize person as people. The same would go for your controller on the Person model, PeopleController instead of PersonsController. Though maybe it will work with persons as well.
:person.to_s.pluralize # "people"
:people.to_s.singularize # "person"
:persons.to_s.singularize # "person"
I have two models, Trip and Day, with a one-to-many relationship. For the time being I do not want to make Day an embedded document.
class Day
include MongoMapper::Document
...
key :trip_id, ObjectId
belongs_to :trip
end
class Trip
include MongoMapper::Document
...
key :day_ids, Array
many :days, :in => :day_ids
end
I would like to be able to create routes that look like this:
/trips/:trip_id/days/:index_of_day
Where :index_of_day would be used to find the nth day in a trip #trip.days[:index_of_day], so a person could easily navigate to the first, second, etc. day of a trip.
Currently my route.rb file looks like this:
resources :trips do
resources :days
end
Which generates the default routes /trips/:trip_id/days/:day_id.
One partway solution I had was to put in my route.rb file
match 'trips/:trip_id/day/:id' => 'days#show'
And then in my Days Controller
def show
#day = Trip.find(params[:trip_id]).days(params[:id].to_i)
...
end
This sort of worked except all of the helpers like trip_day_path automatically redirect using the day id, not the day index.
If you want your helpers to use the index instead of the day id, you can define to_param in your Day model. Rails calls to_param on your object to find out what to put in the URL.
class Day
# ...
# many :in is for many-to-many, but you are using it for one-to-many
# either way, many :in doesn't have an inverse yet
def trip
Trip.first('day_ids' => self.id)
end
def to_param
trip.days.find_index(self)
end
end
You'll notice that's kind of hackish, which is a code smell.
I am attempting to make a search very complicated (of course, to make it easier for users)
I've got an app with 3 models: Campaigns, Businesses and Locations
like so:
\\ campaign.rb
belongs_to :business
has_many :locations, :through => :business
acts_as_mappable
\\ business.rb
has_many :campaigns
has_many :locations
\\ location.rb
belongs_to :business
has_many :campaigns, :through => :business
acts_as_mappable
The way this is set up, there are some businesses that have multiple locations. For those that don't, the geokit info is coded into the campaign database entry. For those that do have multiple locations the geokit info is coded into the location database entry.
I am trying to do a search for campaigns that will return the results within a certain distance. This is simple enough when dealing with businesses that have a single address.
Campaign.find(:all,
:conditions => [blahblahblah],
:origin => address,
:within => distance
)
However, I want to also include campaigns that belong to businesses that have multiple locations. I want the search to return a result for that campaign, if the business has multiple locations, and if any of those locations fall within the bounds. I was thinking something like:
Campaign.find(:all,
:include => [:business, :locations]
:conditions => [blahblahblah],
:origin => address,
:within => distance
)
But this doesn't return any results for campaigns that belong to businesses that have multiple locations. I'm a noob when it comes to SQL so I'm not exactly sure how to have rails search in one model (Campaign), and search across another model (the Business model) to grab results from the Location model. The fact that geokit is involved makes it even more complex.
I tried: acts_as_mappable :through => :locations in the campaign.rb but it just threw an sql error
I messed around with a polymorphic model "addressable" but I found I would pretty much have to start from the ground up on the controllers of the other models.
I've also thought about named_scopes but I believe that geokit does not support them.
Any suggestions?
If you think your model is overly complicated its a good sign that it needs a redesign.
Is it possible to have a campaign without a business? if not, then it doesnt make sense to make campaign act_as_mappable (Im assuming your campaign DB has lat and lng columns) if business already has mappable locations.
If campaign is associated with a business that has multiple locations, whats considered location of the campaign? What do you store in campaigns.lat and lng attributes?
If youre sticking with your datamodel, I recommend to break up your search into multiple commands:
def find_campaigns_near(origin,distance)
locations = Location.find(:all,
:origin => address,
:within => distance
);
# use Hash for uniqueness based on id
campaigns = Hash.new
locations.each do |location|
location.campaigns.each do |campaign|
campaigns[campaign.id] = campaign if campaigns[campaign.id].blank?
end
end
campaigns
end