As an example, I have the following models:
class Location < ActiveRecord::Base
attr_accessible :name, :full_address, :latitude, :longitude, :attr1
geocoded_by :full_address
has_many :stores
after_validation :geocode, :if => :full_address_changed?
end
and:
class Store < ActiveRecord::Base
attr_accessible :attr2, :attr3
belongs_to :location
end
I would like to be able to do a search for all stores that:
are nearby (using Geocoder on Location model)
meets some criteria on attr1 in Location model
meets some criteria on attr2,attr3 in Store model.
How should I go about this?
I'm still puzzled about your relationship setup... but say you had a setup like I mentioned above:
class Store < ActiveRecord::Base
attr_accessible :attr2, :attr3
has_many :locations
end
class Location < ActiveRecord::Base
attr_accessible :name, :full_address, :latitude, :longitude, :attr1
geocoded_by :full_address
belongs_to :store
after_validation :geocode, :if => :full_address_changed?
end
You could do something like this...
locations = Location.near(current_location.to_s, 20).where(:attr1 => 'a value')
stores_that_match = locations.find_all {|loc| loc.try(:store).try(:attr2) == 'value2' && loc.try(:store).try(:attr3) == 'value3' }.map(&:store)
That being said, the last part will be narrowed down using ruby in the code provided above.. If you want to narrow the criteria on the associated model as you're talking about using only a query, you're probably going to have to use ActiveRecord's find_by_sql method and write the query out by hand.
Related
I am trying to write a scope which will get data from two models, haven't done this before, so looking to see if anyone can help me with the theory and the practical.
My two models are defined like so
class Member < ActiveRecord::Base
belongs_to :membership
accepts_nested_attributes_for :membership
attr_accessible :membership_id, :forename, :middlename, :surname, :house_no, :house_name, :street, :town, :postcode, :home_tel, :mobile_tel, :work_tel, :email, :start_date, :expiry_date
end
class Membership < ActiveRecord::Base
has_many :members, :dependent => :destroy
attr_accessible :membership_type, :cost
end
I would like to get all the members who have joined today collating the total amount in £ it has generated
So can i perform a sum in a scope or would i have to write a method that gets the total and then use that method within the scope?
class Member < ActiveRecord::Base
scope :new_memberships_cash_today, ->() {
where(:start_date => Date.today)
}
end
That's as far as i have got so far, not correct I know .Really not sure how to construct the rest, any help appreciated
I presume something like that will suffice:
joins(:memberships).where(:start_date => Date.today).sum('memberships.cost')
Models:
class Location < ActiveRecord::Base
attr_accessible :longitude, :latitude, :radius, :name
has_many :statistics
end
class Statistic < ActiveRecord::Base
attr_accessible :stat_type_id, :updated, :count, :location_id
belongs_to :location
belongs_to :stat_type
end
class StatType < ActiveRecord::Base
attr_accessible :id, :name
has_many :statistics
end
I can update a record like this:
statistic = ##location.statistics.build(:count => 1, :updated => updated, :stat_type_id => ##stat_type.id)
statistic.save
Currently a new record is always created. However, I want to change this so count gets incremented rather than given the value 1.
I know increment exists: http://apidock.com/rails/ActiveRecord/Persistence/increment but I'm not sure how to use it in my case (with this JOIN). How can I use it?
Thanks.
I have a has many through relationship in my app:
Shows has many Bands through => Lineups
Bands are unique by :name
class Show < ActiveRecord::Base
attr_accessible :city_id, :title, :dateonly, :timeonly, :image, :canceled, :venue_attributes, :bands_attributes
belongs_to :city
belongs_to :venue
has_many :lineups
has_many :bands, through: :lineups
has_and_belongs_to_many :users
end
class Lineup < ActiveRecord::Base
belongs_to :show
belongs_to :band
end
class Band < ActiveRecord::Base
attr_accessible :name, :website, :country, :state
has_many :lineups
has_many :shows, through: :lineups
validates :name, presence: true
validates_uniqueness_of :name
before_save :titleize_name
private
def titleize_name
self.name = self.name.titleize
end
end
New Bands are created like this:
(lets say we have a show record already saved called s1)
> s1.bands.new(name: "Wet Food")
> s1.save
Right now this will only save if a band named "Wet Food" doesn't already exist
In which model is the best place to do a Band.find_or_create in this relationship so that an existing band can be used if one with the same name exists?
This is generally the type of call that would go in a Controller (or maybe a service object), but not in a Model. It really depends on the particular user flow that you're trying to accomplish in your app. Basically, where ever you are already using s1.bands.new, you could use this instead :
s1.bands.where(name: 'Wet Food').first_or_create
I'm trying to skip validations of a belongs_to association if a criteria is true. Below is my code
class Venue < ActiveRecord::Base
has_many :events
validates_presence_of :name, :postcode, :category
end
class Event < ActiveRecord::Base
belongs_to :venue
accepts_nested_attributes_for :venue
end
So what I need to do is skip the Venues validates_presence_of validation if a criteria from the Events model is true. So lets say if the event_type was equal to '1' then it will ignore the Venues validates_presence_of call but if event_type was '2' then it will still execute the validates_presence_of call.
There's a Railscast on this topic. You can also check out Rails Conditional Validation
based on the link above, you'll have to pass a lambda such as:
:if => lambda { |venue| venue.event.try(:event_type) == 2 }
In the end I did something very similar to this
I have two models: User and Location as below:
class User < ActiveRecord::Base
attr_accessible :location, :password, :user_name, :password_confirmation
validates :location, :user_name, :presence => true
validates :password, :presence => true, :confirmation => true
has_one :location, :foreign_key => 'location'
end
class Location < ActiveRecord::Base
attr_accessible :loc_id, :loc_name
belongs_to :user, :foreign_key => 'loc_id'
end
You can see that I use the custom foreign_key for the models. I use form builder to build a user sign up form, but when I submit data the error occurs:
Location(#2170327880) expected, got String
I use simple_form to build the form, related code is:
= f.input :location, :collection => Location.all.collect {|c| [c.loc_name, c.loc_id]}
How can I resolve this problem? Or must I use the default foreign_key like location_id for the association?
Thanks.
Update:
When I rename the location field in User model to loc_id and remove the :foreign_key like this:
class User < ActiveRecord::Base
attr_accessible :loc_id, :password, :user_name, :password_confirmation
validates :loc_id, :user_name, :presence => true
validates :password, :presence => true, :confirmation => true
has_one :location, :foreign_key => 'location'
end
class Location < ActiveRecord::Base
attr_accessible :loc_id, :loc_name
belongs_to :user
end
It works fine. But I still want to know how to associate the User and Location model.
P.S. I use Location model to store the country code and country name, which will never update by User.
It sounds like you actually want to have
class User < ActiveRecord::Base
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :users
end
This means that a user has a location_id column. If you do things the other way around (user_id column on location) then a given location can only be associated to one user. The rails way is that location_id on users 'points' at the id column in the locations table. If you want it to point at a different column, use the :primary_key option (The :foreign_key option would be if you wanted the column on users to be called something other than location_id)
In terms of the form, you can't do f.select :location - forms don't know how to transfer a complicated object like that. In these cases you want to set the form to control the location_id attribute, i.e.
= f.input :location_id, :collection => Location.all.collect {|c| [c.loc_name, c.id]}
If you go down the route of having the location id column refer to the loc_id column on location, then you'd need to change that to be
= f.input :location_id, :collection => Location.all.collect {|c| [c.loc_name, c.loc_id]}
Personally if you're only just starting out with rails I'd stick to the defaults
It looks like you're misusing foreign key. In the User model, you should have just has_one :location and the location model should have a user_id attribute. In the location model, you only need to write belongs_to :user. A foreign key is always an index into another (foreign) table.
class User < ActiveRecord::Base
# stuff
has_one :location
end
class Location < ActiveRecord::Base
# more stuff
belongs_to :user
end
If what you ultimately want to do is select all users with the same location, you might want to set the models up a little differently. Right now, because each user has its own location record in the location table, you would end up with duplicate locations, and you'd have to find all of these for a unique location and extract the user_ids from them.
Instead do this
class User < ActiveRecord::Base
has_one :placement
has_one :location, through: :placement
end
class Placement < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :placements
has_many :users, through: :placements
end
. As for the migrations, Placement should have a :user_id and :location_id. You can drop the :user_id that you currently have in Location. What this code says is that we have many users and we have many unique locations, and we place users in unique locations by creating placements, which indicate that a user with :user_id is located in location with :location_id. Also, don't forget to add a the line
add_index :placements, [:user_id, :location_id], unique: true
so that you can't place a user in a location more than once.
EDIT: Forgot to add: you can get all users in a location by simply getting the location record and calling location.users