Incrementing value if it exists in JOIN table - ruby-on-rails

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.

Related

find_or_create on a has many through relationship

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

rails_admin plugin issue with nested form on a has_many :through relationship

I have what seems to be a cookie cutter problem that even has an associated wiki page here: https://github.com/sferik/rails_admin/wiki/Has-many-%3Athrough-association
I'll try to be brief. I have a has_many :through relationship in an application I'm building. The models involved are the following:
Athlete, AthleteRole, SportRole.
The sport_roles table has a list of generic roles that an athlete can have such as first baseman, second baseman, etc.
The athlete_roles table is the many to many join table that contains an athlete_id and a sport_id.
My models are defined below with code examples. I simply want to be able to create an Athlete and associate them with 1+ sport roles (which will ultimately create 1+ new records in the athlete_roles table). It shouldn't ask me for an athlete_id as the athlete won't have an id until save is called on the backend and validation passes. I don't need to be able to create new sport_roles here. We'll assume all roles that the new athlete being created can take on have already been predefined.
** EDIT **
To clarify, my question is, how do I get to pick one or multiple existing sport roles for an athlete using the rails_admin plugin, NOT in a stand alone form? I do not wish to create new sport roles, but I want to be able to pick an existing one or two when creating an athlete and have that data reflected in the athlete_roles table.
Code below.
class Athlete < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all, :autosave => true, :include => :sport_role
has_many :sport_roles, :through => :athlete_roles
attr_accessible :first_name
attr_accessible :middle_name
attr_accessible :last_name
attr_accessible :suffix_name
attr_accessible :birthdate
# FOR RAILS_ADMIN
# for a multiselect widget:
attr_accessible :sport_role_ids
accepts_nested_attributes_for :athlete_roles, :allow_destroy => true
attr_accessible :athlete_roles_attributes
end
class AthleteRole < ActiveRecord::Base
attr_accessible :athlete_id
attr_accessible :sport_role_id
# Associations
belongs_to :athlete
belongs_to :sport_role
# validations
validates :athlete_id,:presence=>true,:uniqueness=>{:scope => [:sport_role_id], :message => "is already associated with this Sport Role"}
validates :sport_role_id,:presence=> true
end
class SportRole < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all
has_many :athletes, :through => :athlete_roles
attr_accessible :name
attr_accessible :short_name
attr_accessible :description
attr_accessible :created_at
attr_accessible :updated_at
attr_accessible :athlete_ids
attr_accessible :athlete_role_ids
validates :name, :presence => true
validates :short_name, :presence => true
validates :description,:length=>{:maximum => 500, :allow_nil => true}
end
I think the problem here resides in your model.
A model like you are describing, is a has and belongs to many relation and should look like so:
athlete:
class Athlete < ActiveRecord::Base
has_and_belongs_to_many :sport_roles
end
sports role
class SportRole < ActiveRecord::Base
has_and_belongs_to_many :athletes
end
The migrations should look like:
athlete
class CreateAthletes < ActiveRecord::Migration
def change
create_table :athletes do |t|
t.timestamps
end
end
end
sports role
class CreateSportRoles < ActiveRecord::Migration
def change
create_table :sport_roles do |t|
t.timestamps
end
end
end
relation between athletes and sports roles
class SportRolesAthletes < ActiveRecord::Migration
def change
create_table :sport_roles_athletes , :id => false do |t|
t.integer :sport_role_id
t.integer :athlete_id
end
end
end

Something seems to be broken in my belongs_to, has_one relationship

I have a data model that looks like this:
A customer has subscription_id and setup_id as parameters. In some cases, customer will only have one of the parameters. In other cases, it will have both.
Currently, if I make a new customer through either the subscriptions flow or the setups flow, either Subscription.last or Setup.last will reflect the most recent customer that was created (with customer_id equalling the last customer created)
However, I am having the problem of Customer.setup_id or Custumer.subscription_id being nil in all cases.
Here's my code from both subscription.rb and setup.rb:
class Subscription < ActiveRecord::Base
attr_accessible :status, :customer_id
belongs_to :customer
end
class Setup < ActiveRecord::Base
attr_accessible :status, :customer_id
belongs_to :customer
end
And in customer.rb:
class Customer < ActiveRecord::Base
attr_accessible :email, :name, :stripe_token, :subscription_id, :setup_id, :phone, :plan
has_one :subscription
has_one :setup
end
I'm not sure what I'm doing incorrectly here but I'd love it if the three data models could talk to each other correctly.
Edit: Is it bad that both setup and subscription belong to :user rather than :customer?
Edit 2: Updated the code of setup.rb and subscriptions.rb to correctly reflect the data model currently. And customer.rb is still not recognizing the correct setup_id or subscription_id
class Subscription < ActiveRecord::Base
attr_accessible :status, :customer_id
belongs_to :customer
end
class Setup < ActiveRecord::Base
attr_accessible :status, :customer_id
belongs_to :customer
end
class Customer < ActiveRecord::Base
attr_accessible :email, :name, :stripe_token, :phone, :plan
has_one :subscription
has_one :setup
end
customer = Customer.first
customer.subscription # Instance of Subscription that belongs to customer
customer.setup # Instance of Setup that belongs to customer

How to use foreign key in model and form builder?

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

Rails Find Through Association, Location

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.

Resources