In my omniauth & omniauth-identity based app I came across the follwoing issue:
There are three tables that manage Userdata:
# Stores User data _not_ authentication data
class User
include Mongoid::Document
has_many :identities
field :email, type: String
end
# Stores Authentication data (i.e. Facebook)
class Identity
include Mongoid::Document
belongs_to :user
end
# Stores Authentication data for omniauth-identity
class LocalIdentity < Identity
include OmniAuth::Identity::Models::Mongoid
field :email, type: String
end
The issue is now that I have duplicated the email-field in LocalIdentity. So whenever a user changes his email-address in the User model I need to synchronize the email address. This seems trivial at first but can get real messy as soon as there are more fields involved etc.
So my question is: Is there a way to remove the redundance between User and LocalIdentity?
While pondering about a solution i came to the following conclusions:
Multi Inheritance isn't supported so LocalIdentity < Identity, User won't work
Having a LocalIdentity embedded in every User and
write-trough the values won't work b/c validations wouldn't work
Related
I want to write a method that creates a bunch of almost-duplicate records, just with one or two parameters changed. I'll make a form to control those parameters, I'm just wondering about how best to write the method, and where do keep it.
Presently in my document.rb I've written this:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create(:component_ids => components, :message => 'Message.', :template_id => template.id, :user_id => user.id)
end
end
It doesn't feel right though. Is there a better way to do this?
This code is fine if your security model allows all these fields to be bulk assignable by mention in attr_accessible in the model. If it doesn't then you're better off using the block form of create. Also, if Document, Template and User are ActiveRecord instances, you should let Rails manage the details of ids.
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.component_ids = components,
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
One final note is that component_ids must be serialized to store a list. This is probably a flaw in your model design. The better way is (probably) to specify Component belongs_to User and also User has_many Components. I.e. Component contains a foreign key to User. If it's necessary for a Component to belong also to many users, then you'll need either has_and_belongs_to_many or has_many ... through. The Rails guide on relations describes all this in more detail.
With the right relations set up, the code will become:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.components = components, # Components is now a list of active records.
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
The resulting SQL will get all the foreign keys and (if necessary) relation tables filled in correctly.
I have an associations like:
class Contact
has_many :addresses
has_many :email_addresses
has_many :phone_numbers
end
I want to save all the records (address, email and phones) in once single save statement. For that I wrote following code.
contact = Contact.new(contact_params)
contact.addresses.build(address_params1)
contact.addresses.build(address_params2)
contact.email_addresses.build(email_params1)
contact.email_addresses.build(email_params2)
contact.phone_numbers.build(phone_params1)
contact.phone_numbers.build(phone_params2)
contact.save
It does save the contact, but not saving other records. What am I missing here?
NOTE: I am not using any form to save data. I am importing data.
Add validates_associated in your contact model and then check
validates_associated :addresses, :email_addresses, :phone_numbers
First, In my opinion, you just CAN'T do that.
If you want to save addresses, you need a contact_id for each of them.
And, when you save contact, rails will first validate contact and all sub-objects. This means
contact.valid? must be true before you can save any record.
Since contact does not have an id before save to db, contact_id in any of addresses is empty.
Therefore, contact.valid? will always be false as long as you need to CREATE new contact and its sub-objects at the same time
To summarize, here is the steps of rails:
validate contact itself, success!(NOTE: note saved)
validate address for each of contact.addresses, contact_id not provided, fail!
Second, my suggestion about your problem
About your problem "It does save the contact, but not saving other records.", you need to tell rails that you want to save sub-objects(as shown bellow). Otherwise, rails will ignore other objects. However, even though you did this, you would also meet the problem I described above.
#contact.rb
accepts_nested_attributes_for :addresses
...
My suggestion: you can use transaction.
creat contact
build all sub-objects
save contact again
code sample:
#contacts_controller.rb
Contact.transaction do
contact = Contact.new(contact_params)
contact.addresses.build(address_params)
...
contact.save
end
Is there any way of making the authentication_keys configuration conditional depending on the user type?
For instance in my User model I have two different user, standard user and student, defined by the value of a bitmask attribute. I want to authenticate Students on the basis of username and school_id. Standard users don't have to have a value for school_id. In order to accommodate this I have two types of login screens, one for students and another for standard users. The student login screen has a hidden school_id field that authenticates them to their school.
In my devise configuration file I have:
config.authentication_keys = [ :username, :school_id ]
However with this setup standard users are unable to login, my guess is that it's expecting a school_id for all Users being authenticated. Is it possible to make the authentication keys conditional? Or give an allow_blank option for school_id? I want to be able to keep standard users and students in the same model.
Yes, simply do in your models:
class User < ActiveRecord::Base
devise :database_authenticatable, authentication_keys: [:username]
end
class Student < ActiveRecord::Base
devise :database_authenticatable, authentication_keys: [:username, :student_id]
end
I am working with a Rails polymorphic inheritance configuration - I have the following setup:
User < ActiveRecord::Base
belongs_to :rolable, :polymorphic => true
Student < ActiveRecord::Base
has_one :user, :as => :rolable
accepts_nested_attributes_for :user
Teacher < ActiveRecord::Base
has_one :user, :as => :rolable
accepts_nested_attributes_for :user
I want to be able to capture email address for teachers and a username for students (who won't typically have an email address). I defined those as attributes of the User model, but now I'm stuck when I try to do validations for Student and Teacher. I didn't define them in their respective models because I'm using Devise and there will be other user types. Abstracting what is currently type to a Role pattern isn't a good fit for my particular scenario either.
Since username and email are properties of User what I basically want to do is check if the rolable_type field from the polymorphic relationship is type student and if so, make username required and email not, but in the new method that property isn't set. However Rails 'knows' this is a Student, so it feels like there's some way to check the instance type. The closest link I've found to what I'm shooting for is the third comment to the accepted answer in this question: How to apply different validation rule according to polymorphic association type (Rails)?, but I'm having trouble getting the method_missing syntax correct as I'm not experienced with metaprogramming. Am I on the right track with this? Or is there a simpler way? Or should I move the properties to the polymorphic models instead?
I made a relationship with the three models using has_many :through:
class Curriculum class < ActiveRecord::Base
has_many :interests
has_many :vacancies,: through => :interests
end
class Vacancy class < ActiveRecord::Base
has_many :interests
has_many :resumes,: through => :interests
end
class Interest < ActiveRecord:: Base
belongs_to :vacancy
belongs_to :curriculum
end
And to create curriculum and vacancy, I create them by administrative, i need to know how can i create the interest to the id of the vacancy, and how it will be logged on the system I have to get the id of it and make the relationship in creating a new bank interest. I wonder how I can program it to do so, and I wonder how the controller will get the create action, and what better way to do this.
First, try to read the whole "Guide to Rails on Associations", especially the part about has_many :through. Then check your schema if your db is migrated and contains for the table interests the necessary foreign keys to curriculums and vacancies called curriculum_id and vacancy_id.
If that is all in place, the following code will create the relationship between two objects:
#curr = Curriculum.find(1)
#vac = Vacancy.find(1)
#curr.interests << #vac
#curr.save
The last two lines creates an interest between #curr and #vac and store that on the database. So you should not use IDs and handle them directly, but work with objects instead.
The second part now is to provide a UI to allow the definition (and removal) of interests between curricula and vacancies. The base flow here is:
You have one curriculum in focus.
You have a link to add / remove curricula.
The view that opens shows a list of possible vacancies, where every vacancy has a checkbox.
By selecting (or deselecting) the check boxes, the IDs of the vacancies will be held in the params of the request sent to the controller.
See the (older) podcast Railscast #52 how to do that in a similar context. Or see the example for has_many :through with checkboxes.
An alternative way would be to use JQuery autocomplete, and add so interests one-by-one. See the nice podcast Railscast #258 which uses JQuery Tokeninput for that.
I think this is what your looking for:
HABTM Checkboxes
That's the best way to use an Has and Belongs to many association.