I've inherited quite a weird table layout:
callbacks
id, note, user
admin
id, name, password
In callbacks, the user is set to the name of the admin rather than the actual ID. Now I need to be able to call callbacks.user and have rails lookup the admin with that name and then bind it to that record.
I have a model for admin that is called users
How would I go about that?
You can override the default methods.
def user
User.find_by_name(user_name)
end
def user=(obj)
self.user_name = obj.name
end
def user_name
self[:user]
end
def user_name=(name)
self[:user] = name
end
Other option , to make it work with belongs_to, there is primary_key option but need to have a different name than the attribute user
# Callback.rb
belongs_to :user_model , :class => "User", :foreign_key => :user, :primary_key => :name
# User.rb
has_one :callback , :foreign_key => :user, :primary_key => :name
Related
In RoR, whenever you create a nested resource, is there to set attributes during creation of a resource with a parent association, within the model?
I have this Role model that may belong_to and have_many other roles.
employee = Role.find_by_slug :employee
employee.role
=> nil
employee.roles
=> [...more roles...]
waitress = employee.roles.create(slug: :waitress)
=> #<Role id...
waitress.role
=> #<Role slug: 'employee'...
waitress.roles
=> []
The role model has a boolean attribute of subtype. Whenever I create a role from an existing role, I'd like for subtype to be set to true.
employee.subtype
=> false
And waitress would look like this:
waitress.subtype
=> true
Whenever I create a role from an existing role, I'd like for subtype to be set to true.
#app/models/Role.rb
class Role < ActiveRecord::Base
belongs_to :role
has_many :roles
validate :role_exists, if: "role_id.present?"
before_create :set_subtype, if: "role_id.present?"
private
def set_subtype
self.subtype = true
end
def role_exists
errors.add(:role_id, "Invalid") unless Role.exists? role_id
end
end
The above will require another db request; it's only for create & it will happen when the model is invoked (IE you can call it whatever you like when you need it).
--
An alternative to this would be to use acts_as_tree or a similar hierarchy gem.
AAT adds a parent_id column in your db, to which it will then append a series of instance methods you can call (parent, child, etc).
This would permit you to get rid of the has_many :roles, and replace it with a children instance method:
#app/models/role.rb
class Role < ActiveRecord::Base
acts_as_tree order: "slug"
#no need to have "subtype" column or has_many :roles etc
end
root = Role.create slug: "employee"
child1 = root.children.create slug: "waitress"
subchild1 = child1.children.create slug: "VIP_only"
root.parent # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1
According to your description, a given Role is considered a subtype if it has no parent role. In this case, simply add the following method to Role:
def subtype?
!self.role.nil?
end
The following changes did the trick for me:
from:
has_many :roles
to:
has_many :roles do
def create(*args, &block)
args[0][:subtype] = true
super(*args, &block)
end
end
Currently, I can add the creator_id like this in my controller:
#entry = Entry.new(params[:entry].merge(:creator => current_user._id))
If this is my model:
class Entry
include Mongoid::Document
belongs_to :User
field :creator, :type => String
field :title, :type => String
field :content, :type => String
field :scorea, :type => Integer
field :scoreb, :type => Integer
field :scorec, :type => Integer
end
Is there a better way to do this?
Your model doesn't looks very good, do you really want to store the user_id in a string field?
I suggest you change your models to following:
class Entry
include Mongoid::Document
belongs_to :creator, :class_name => 'User', :inverse_of => :entries
# field definitions
end
class User
include Mongoid::Document
has_many :entries, :inverse_of => :creator
end
Once you change the models you can continue using what you are now or alternatively:
#entry = current_user.entries.build(params[:entry])
Update:
The method to initialize entry is not much different in the way I did it. It is just more towards the rails way of doing things. The main difference is that you were not using the associations. From your model definitions it is clear that you want a one-to-many association between user and entries and this is how you create such associations. Associations has a lot of goodies attached to them, like you can do following things:
user.entries << entry # add a entry to users, will automatically change entry.creator_id
entry.creator = user # sets creato_id = user_id
entry.creator # returns associated user. no need to do User.find(entry.creator_id)
user.entries # returns all entries for use <=> Entry.where(creator_id: user.id)
for more details go to http://mongoid.org/docs/relations.html
There are no better way to do. Or you can do it in 2 line :
#entry = Entry.new(params[:entry])
#entry.creator = current_user._id
this second case allow you to add creator field in attr_protected
You can do it in this manner
#entery = Entery.new(params[:entry])
#entery.creator = current_user
or
#entery.creator_id = current_user.id
hence you can assigned id of creator to this entery.
I have a ruby (on rails) class:
class User < ActiveRecord::Base
# relationships
belongs_to :current_shipping_address, :class_name => "Address"
belongs_to :subscription
# Validators
validates_presence_of :subscription
validates_presence_of :current_shipping_address
end
I do this in a controller:
subscription = Subscription.new
address_info = params[:user].delete(:address) rescue {}
#address = Address.new(address_info.merge(:country => "US"))
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.subscription = subscription
#user.current_shipping_address = #address
#user.save!
At this point, incredibly, I have a #user that has been saved in the database but has no current_shipping_address (despite the validation). The subscription has also been saved in the database.
The address does NOT get saved.
What am I missing here?
1 - how does user get saved without the validation failing?
2 - why is the address not saved?
How can I alter this code so that the address gets saved (as I would expect it to)?
I am running this under ruby on rails 3.
Thanks!
You cannot have subscription and current_shipping_address saved by user in your case because, they are not simple fields in model User. You define them as model associated to User through belongs_to, I'm not sure about what you are willing to do, but if I understand correctly one way to do it is using nested attributes:
class User < ActiveRecord::Base
# relationships
has_many :current_shipping_addresses, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
# Nesting
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :current_shipping_addresses
end
After that, when you then create and save a User, a subscription and current_shipping_address are saved whith it .
More on assocations here : http://guides.rubyonrails.org/association_basics.html
You need to tell it what the foreign key is if you're not sticking with the standard table structure. You'll just need to add:
belongs_to :current_shipping_address, :class_name => "Address", :foreign_key => "address_id"
or whatever column you are using to store the address id to the address table.
This is not the recommended way of doing nested attributes though. I would recommend using a fields_for in your form rather than using the lines:
address_info = params[:user].delete(:address) rescue {}
#address = Address.new(address_info.merge(:country => "US"))
You can just do
<%= f.fields_for :current_shipping_address do |ff| %>
# ... your address fields...
<% end %>
which will then let you simply save the address when you run #user.save!
You can still add the :country => "US" beforehand with
params[:user][:current_shipping_address][:country] = "US"
and then run save. Its really up to you though.
Try this way!
subscription = Subscription.new
address_info = params[:user].delete(:address) rescue {}
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.subscription = subscription
#user.current_shipping_address << Address.new(address_info.merge(:country => "US"))
#user.save!
Seems the problem was that address was actually failing to save. not because of a validation but because of an error in a 'before_create' method (and yes I know I didn't give you the address object... I didn't think it important at the time!).
class Address < ActiveRecord::Base
# relationships
# Validators
validates_presence_of :city, :state, :country, :first_name, :last_name, :address_1
before_create :check_state
before_create :check_country
def check_state
retval = true
state.upcase!
if country == "US" and !US_STATES.map{|s| s[1]}.include?(state)
errors.add(:state, "Must be valid")
retval = false
end
retval
end
end
Check state was failing. But that meant that address passed the 'valid?' call, which it seems is all active record cares about. (This method really should be a validation)
I have switched to doing this (thanks enokd for the link!):
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.build_subscription(:subscription_plan_id => #subscription_plan.id)
#user.build_current_shipping_address(address_info.merge(:country => "US"))
I haven't bothered to investigate fully, but, if address fails to save it stops the whole #user.save!
Personally I think this is a little bit of bug perhaps or certainly an unexpected behaviour, but what do I know!
Try:
class User < ActiveRecord::Base
# relationships
has_one :current_shipping_address, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
validates :current_shipping_address, :presence => true
end
I'm having trouble with the following code:
User < AR
acts_as_authentic
belongs_to :owner, :polymorphic => true
end
Worker < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
Employer < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
I'd like to create registration forms based on user types, and to include authentication fields such as username and password. I currently do this:
UserRegistrationController < AC
#i.e. a new Employer
def new
#employer = Employer.new
#employer.build_user
end
...
end
I then include User fields with fields_for. All views render fine, but here's the catch: I cannot build a User, it tells me :password is a wrong method, so I guess the authentication logic has been bypassed.
What should I do? Am I doing it wrong altogether? Should I drop polymorphic associations in favor of Single Table Inheritance? Whatever I do, I have to make sure it plays nicely with Authlogic.
I'd approach the building of new users of either type in the opposite direction. ie:
#controller
#employer = Employer.new
#user = #employer.build_user
#view
form_for #user |f|
f.text_field :login
f.password_field :password
fields_for :owner, #employer |f_e|
f_e.some_field :some_value
#controller
def create
#owner = params[:owner][:some_employer_field_or_virtual_attribute] ? Employer.new params[:owner] : Worker.new params[:owner]
#owner.save
#user = User.new(params[:user].merge!(:owner => #owner)
if #user.save
...
re. mentioned virtual attribute - if there's no field in the model, and thus in the form, which distinguishes user type as employer or worker then set an virtual attribute within each which you can put as a hidden boolean field in the form
I have the following join table that works:
class CreateRolesUsers < ActiveRecord::Migration
def self.up
create_table :roles_users,:id => false do |t|
t.integer :role_id, :null => false
t.integer :user_id, :null => false
end
end
def self.down
drop_table :roles_users
end
end
But I don't know how to load by default some data (syntax), I try that:
roleUser = RoleUser.create(:role_id => 2,:user_id => 1)
roleUser.save!
It does not work, is it RoleUser... or something else to use? RolesUser...etc.
That's provided that you have a model named RolesUser.
If you have a habtm association, the model is probably not there.
One way of loading roles could be;
user = User.create :name => 'John'
role = Role.create :name => 'admin'
user.roles << role
From what I understand, your user can have many roles, right? If so..
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
Then you can do
user = User.create :name => 'John'
role = Role.create :name => 'admin'
roles_users = RolesUser.create :user => user, :role => role
The has_and_belongs_to_many assoiation creates a join table with both FK. If you need extra data in the join table, you will need to use has_may :through instead of has_and_belongs_to_many.
I strongly recommend reading the guide on ActiveRecord associations. Good luck!
You're almost there. The table name is a plural form of the model name. The table is named RolesUsers, so the model is named RolesUser.
Also, I think you would prefer to use the new method if you're going to call save! after the fact. Calling create automatically saves the record.
rolesUser = RolesUser.new(role_id => 2,:user_id => 1)
rolesUser.save!
Recommended reading: http://api.rubyonrails.org/classes/ActiveRecord/Base.html
First when you have association has_and_belongs_to_many you actualy don't have model, you only have database table with plural name combined from models that are participating in association, for example
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
You don't get model named UserRoles, or UsersRoles, or UsersRole or any kind of model, this only makes some methods that you can use on User instance or Role instance to find for 1 user all of his roles, or to find all users with some role ant etc.
This works, that rails will look for database table with name roles_users (it looks for table that is combined with both model names in plural, ordering by alphabetical order, thats why its roles_users and not users_roles).
For particular user you can add roles or predefine existing ones, example:
# find one user
user = User.first
# get collection of roles
roles_c = Role.where("some conditional statement to find roles")
# set user roles to found collection
user.roles = roles_c
# save changes
user.save
This way you will get records in roles_users table with user_id of user and for every role in roles_c collection there will be record, for example:
# if user.id is 1
# and in roles_c you have 3 roles with id_s 2,5 and 34
# in roles_users table there will be created 3 records with
user_id => 1, role_id => 2
user_id => 1, role_id => 5
user_id => 1, role_id => 34
Other way is to add some roles,
user = User.first
roles_c = Role.where('conditional statement')
user.roles << roles_c
user.save