join table with includes creating problem - mysql

Models structure:-
User has_many :subscriptions
Blog has_one :coupon
Subscription has_one :coupon
I am including the tables
User.includes(subscriptions: :coupon, :blogs)
I am trying to get all users data and only get the valid subscriptions corresponding to it.
If i do
User.includes(subscriptions: :coupon, :blogs).where(:state => 1).references(:subscriptions)
then i am getting the users having only valid subscriptions
So how to get all the users with preloaded blogs coupons and included with valid subscriptions???

If you want to load all users, including those who don't have any valid subscriptions, but for each user be able to access only valid subscriptions, you need to define another association valid_subscriptions. It can be done this way:
In user.rb
has_many :valid_subscriptions, -> { valid }, class_name: 'Subscription'
In subscription.rb, define what being valid means.
scope :valid, -> { where(state: 1) }
Then you query can be User.includes(:blogs, valid_subscriptions: :coupon)

Related

Only one principal(user) can be created by role attribute which is enum + rails

i am new to rails and creating a school app where i have given an attribute role to user which is deciding the role of user and i want to create only one user can be principal(role), how do i achieve that there can not be create more than one principal user in app,
i am using devise for authentication...
i tried this =>
validates :role, :uniqueness => { :role => "principal" }
but it is restricting me from creating any kind of user , in my user enum i have given 4 roles [admin,principal,teacher,student]
any help or advice will be appreciated.... thanks in advance
I would use a custom validation like this:
validate :one_principal_exists
private
def one_principal_exists
return unless principal?
return unless User.where.not(id: id).exists?(role: 'principal')
error.add(:role, 'already exists')
end
The idea is to check first if the current user is a principal, if not skip the validation. And then check if there is already another user (who has a different id than the current user) in the database who has the principal role. If such a user exists, then add a validation error to the role attribute.
Please note that you might need to add more conditions to the database query when you, for example, want to support multiple schools and each school has a different principal.
in model use below code
user.rb
before_create :check_principle
def check_principle
if User.find_by(role: "principle")
errors.add(:code, "principle already exist")
return false
end
end

Sorting associated objects based on the association's creation date

Right now, I'm working on a simple app. It requires to get the associated objects ordered by the date that they we're added to the object. For that, I want to order them based on the pivot-table's id.
My app looks a bit like this:
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
However, when a user wants to buy a product, I would add a new relation into the pivot table courses_users. When I then run #product.users, I will get them back in the order the users where created, not added as the relation.
I've tried creating a query scope, but it didn't work. I also tried to create a order on the has_and_belongs_to_many, as such:
has_and_belongs_to_many :users, order: 'course_users.id ASC'
But none of that seemed to work, no ORDER statement could be found in the logs.
Add the created_at field to your table.
rails g migration AddTimestampsToCourseUsers created_at:datetime
then you can
#product.users.order "course_users.created_at ASC"

Preventing duplicates via a custom foreign key in has_many :through

I'm trying to implement a two-way has_many :through association between a User model and a Location model using a UserLocations join table. This will enable setting user locations with built in ActiveRecord methods, ie. #user.locations = [<Location 1>, <Location 2>, ...]. My goal is to not associate locations to users individually, but rather for users to add and remove them, one or more at a time, via another field: :zip_code. This means that when a user adds a zip code, ActiveRecord should insert a single record into UserLocations (something like INSERT INTO user_locations (user_id, zip_code) VALUES (1, 12345)). Then, when #user.locations is called, ActiveRecord should join by :zip_code and get the matching location(s). My current implementation works, except that one INSERT into UserLocations is generated for each location associated with a zip code.
class User < ActiveRecord::Base
has_many :user_locations
has_many :locations, through: :user_locations
end
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location, primary_key: :zip_code, foreign_key: :zip_code
end
class Location < ActiveRecord::Base
has_many :user_locations, primary_key: :zip_code, foreign_key: :zip_code
has_many :users, through: :user_locations
end
Things I've tried:
validates_uniqueness_of :zip_code, scope: :user_id - just throws a validation error and prevents all record creation
has_many unique: true - doesn't prevent duplicate DB queries
add_index unique: true for (user_id, zip_code) - would at least prevent duplicate entries from being created, but I'm trying to prevent unnecessary queries entirely
Using questions like this one for guidance hasn't gotten me any closer. Is what I'm trying to do possible without using my own methods to get/set user locations?
First of all, I'm not very experienced in rails yet, but I'll still try to help :)
What I would do is not using zipcodes as a key. When a user inputs zip codes you look up the code in the Location:
#zip_code = Location.where(zipcode: user_input).first
#zip_code.user_locations.create!(user_id #some other stuff you want)
This way you store the id of the location into the user location and no duplicates are made. You can then generate user locations by joining the UserLocation and Location.
But as I said, there may be a better way of doing this as I'm beginner.
Stop me if I'm wrong :)
You have zipcodes in your locations table (i.e: 111, 222, 333) When a user selects a zipcode of '111' for him self, his record is associated with the existing locations record; but when a user selects a zipcode of '444' a new locations record is created and link to that user. Next use that selects '444' will be linked to this same record.
If my assumption if correct, you should have:
validates_uniqueness_of :zip_code (without scope) in your Location model
in your User model while creating/updating you could use Location.find_or_create_by(params[:zipcode])
This is pseudo-code (don't copy-paste it), I don't exactly know how your code is writen, but my point is for you to have a look at find_or_create, I believe it could be your solution
It looks like you have the association setup correctly.
When you have a has_many association in rails and want to do something like this:
#user.locations = [<Location 1>, <Location 2>, ...]
Rails will create individual INSERT statements for each location in the array, although it will do a bulk DELETE for you. If you want it to do bulk INSERT statements, you'll need to roll your own code or look into the activerecord-import gem to do this.
As for the duplicates, if you are only doing the above code, there shouldn't be duplicate record errors unless there are duplicates in that location array, in which case you should call uniq on it first.

DataMapper- can I avoid intermediate tables?

I am a total beginner at DataMapper, and have two models:
class ThirdPartyAccount
include DataMapper::Resource
property :access_token, String, :length => 500
belongs_to :user
end
class User
include DataMapper::Resource
property :id, Serial
property :first_name, String
has n, :third_party_accounts, :through => Resource
end
Looking at the SQL logs, it appears to create two tables- users, third_party_accounts and third_party_account_users to join the two. It doesn't appear that the last table is needed- surely the third_party_account table just needs to use it's user_id field to map directly to the user table? Have I accidentally created a many-to-many relationship here?
It's due to this line:
has n, :third_party_accounts, :through => Resource
:through => Resource tells DataMapper to that it's a "has-and-belongs-to-many" relation (each 3rd party account belongs to multiple users and each user has multiple 3rd party accounts), which requires an intermediate table. If this is just a has-many relation (each user has many 3rd party accounts, but each account only belongs to one user), you should just use:
Class User
...
has n, :third_party_accounts
end
See http://datamapper.org/docs/associations.html for more info.

Populating XML file data to db in Rails

In order to work with some client information I have been parsing a 3MB XML file with hpricot... but hpricot takes quite some time to parse the file on a regular basis.
I am thinking about populating this data to a MySql db (once a week) so that I can work with the data directly on mysql with rails.
The file is basically is an XML Google Contacts File that contains client information: Name, email, notes... but also some contact contain multiple value fields such as addresses, telephones.
Currently when I am parsing the data I generate a Contact class
class Contact <
Struct.new(:name, :email, :telephones, :addresses, :user_address,:notes)
end
telephones and addresses contain an array with the different values.
I guess that if I want to recreate this structure in the mysql database I would need to create three tables: contacts, telephones and addresses...
class Contact < ActiveRecord::Base
has_many :addresses
has_many :telephones
end
class Telephone < ActiveRecord::Base
belongs_to :contact
end
class Address < ActiveRecord::Base
belongs_to :contact
end
How would you to populate the Contact class data to the database tables?
Is there a way to insert the data directly from the XML file to the database tables?
Any advice and guidance will be greatly appreciated :)
Thanks!
First why not give nokogiri a try and see if its faster ?
Rails thought people best practices and they came to believe that there is a recipe on how one should program for any given problem. Unfortunately this is not the case, for the usual 90% of tasks there is no magic.
So if you have a contact with some addresses and some telephones it's just that.
Here it's how I would do:
Parse the XML file, if it's too big, stream the parsing.
For each contact in it output a hash just like the params[:contact] would usually turn out in a controller after a form is submitted, and have the Contact model use accepts_nested_attributes_for.
contact = {
:name => xxx,
:user_address => xxx,
:notes => xxx
:addresses_attributes => [
{:some_attribute => xxx, :some_other_attribute => xxx}
...
],
:telephones_attributes => [
{ :some_attribute => xxx, :some_other_attribute => xxx}
...
]
}
Now all that remains is:
Contact.create(contact)