I have 3 models: house, tenant, and plumber. tenant belongs_to house and house has_one tenant. I want to use a custom EachValidator that requires data from plumber. To accomplish this, in my tenant model:
attr_accessor :plumber_limit
In the controller, I have set the plumber_limit with:
house.tenant.plumber_limit = plumber.value
When the tenant validation fires, the tenant.plumber_limit value is nil and, consequently, fails. I've tried adding a public definition for plumber_limit. I've tried adding attr_accessible :plumber_limit. I've tried different notations. Is this failing because tenant is being used in a child context? If so, how do I get the plumber_limit into the tenant's validation?
By request, here's the validation code:
class UniquePlumberAssignment < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.property.limit(record.plumber_limit).each |p|
...
end
end
end
Related
Basically i have user registering himself to the app , by using devise gem.
Instead of having standard sign up form like (email, password) i have an extra 2 fields (name, contact_nr) in total used (name, contact_nr, email, password, password_confirm) fields, :name and :contact_nr attributes exists in 'clients' table only.
Table name: clients
id :integer not null, primary key,
name :string(255)
surname :string(255)
contact_nr :string(255)
user_id :integer
class Client < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :client, dependent: :destroy
after_create :update_user_client
def name
return unless client
client.name
end
def contact_nr
return unless client
client.contact_nr
end
def update_user_client
Client.last.update_attributes(user: self)
end
end
In my RegistrationsController I have only one method
class RegistrationsController < Devise::RegistrationsController
before_action :create_client
private
def create_client
return if params[:user].blank?
Client
.new(name: params[:user][:name],
contact_nr: params[:user][:contact_nr])
.save(validate: false)
end
end
What bothers me is that kind of writing code, it feels like code smell.
How would you implement it?
Thanks guys looking forward to your answers..
First advice I can give is do not separate client and user into two tables if you don't have valid reasons and/or requirements for now. That would make things much easier.
If you have valid reasons, here are my advices on how to improve your existing state of this code piece:
Rails and all mature gems around it rely on 'convention over configuration', so you should check if there are conventional ways to achieve same results.
In your RegistrationsController instead of doing params[:user].blank? check, you should use Devise's way of doing this, provided with inherited methods as devise_parameter_sanitizer.permit within a before_action callback.
Instead of creating client in your controller, move that to model logic, and in your user model put accepts_nested_attributes_for :client.
Since both of your models(client and user) share same name, put a before_save callback, so that you can pass user's name attribute to client itself.
after_create callback is very risky, since it is not an atomic save (no guarantee that client will be updated after user record is updated.). So don't use it. accepts_nested_attributes_for will handle both create and update calls.
If name attribute for user would be fetched through client only, there is no need to keep name within user.
If you want to access client's contact_nr and name attributes directly from user model, then use delegate method inside it.
Putting all together, I would refactor that code piece as this:
class User < ActiveRecord::Base
has_one :client, dependent: :destroy
accept_nested_attributes_for :client
delegate :name, to: :client
delegate :contact_nr, to: :client
# optional. if you want to keep name attr in both models.
before_save :sync_names
private
def sync_names
self.client.name = name if client.present?
end
end
class RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
protected
def configure_permitted_parameters
added_attrs = [:name, :email, :password, :password_confirmation, client_attributes: [:contact_nr]]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
end
end
Don't forget to update your signup and account update forms to accept nested attributes for client resource.
As far as you are validating the data with JS and filtering with params.require(:client).permit, the code looks fine. Try to create many differente scenarios in your Rspec. The test usually reveals unexpected flaws.
I have three models, Account, User and Contact:
class User < ActiveRecord::Base
has_one :account
has_many :contacts, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :account
end
I'm trying to scope build a new contact through the user record, like this in my contacts controller.
def create
#contact = current_user.contacts.build(params[:contact])
respond_to do |format|
if #contact.save
...
else
...
end
end
end
When I do this, I don't receive any errors, the contact record is saved to the database however the account_id column is not set on the contact, and it is not added to the collection so calling #current_user.contacts returns an empty collection.
Any suggestions?
Using build makes a new instance of Contact in memory, but you would need to manually set the account_id on the record (e.g. #contact.account_id = current_user.account.id), or perhaps set it in a hidden field in the new form used to display the contact for creation such that it is picked up in the params array passed to the build method.
You might also want to consider whether accepts_nested_attributes_for may be helpful in this case. Another option may be to use delegate, although in both cases, your use may be sort of the opposite of what these are intended for (typically defined on the "parent").
Update:
In your case, the build method is added to both the User instance and to the Account (maybe "Owner") instance, because you have both a many-to-many relationship between User and Contact, as well as a one-to-many relationship between Account and Contact. So to get the account_id I think you would need to call Account's build, like
#contact = current_user.accounts.contacts.build(params[:contact])
Does this work?
I'm trying to make a basic checkout page, and here's what I have so far:
The checkout is hosted off of transactions#new, and the form is built off of a new Transaction object. Transaction has a number of nested models underneath it:
class Transaction < ActiveRecord::Base
# ...
accepts_nested_attributes_for :user, :shipping_address, :products
# ...
end
User, Product, and Location (Shipping Address) can be persisted when you arrive at the checkout page, depending on the user flow. Product is always persisted upon arriving at the checkout page.
This setup works for me so far except on the failure cases. I've been trying to re-create the new Transaction record (with the previously entered in user info) to display the appropriate error messages, and I had tried doing this in my controller:
class TransactionsController < ApplicationController
def new
#transaction = Transaction.new
end
def create
#transaction = Transaction.new params[:transaction]
# ...
end
end
But I'm getting this error:
ActiveRecord::RecordNotFound in TransactionsController#create
Couldn't find Product with ID=1 for Transaction with ID=
Request Parameters
{"utf8"=>"✓", "authenticity_token"=>"blahblahblah",
"transaction"=>{"products_attributes"=>{"0"=>{"id"=>"1",
"quantity"=>"1"}}}}
Does anyone know what's up with this? Let me know if you need anymore info about my setup here... tried to pare this issue down to the bare essentials...
class Transaction < ActiveRecord::Base
has_many :product_transactions
has_many :products, :through => :product_transactions
end
and
class Product < ActiveRecord::Base
has_many :product_transactions
has_many :transactions, :through => :product_transactions
end
and
class ProductTransaction < ActiveRecord::Base
belongs_to :transaction
belongs_to :product
end
So, the reason you're getting that error is because you're supplying an id with products_attributes, since you're using accepts_nested_attributes_for the product with that id HAS to already be in the association. This is because the products_attributes= method is expecting to either create or modify the records in the products association.
Since the Product is already persisted and you're just trying to create the ProductTransaction you would need change your accepts_nested_attributes_for to include :product_transactions instead.
This part of your question threw me off
User, Product, and Location (Shipping Address) can be persisted when you arrive at the checkout page, depending on the user flow. Product is always persisted upon arriving at the checkout page.
I don't know if you need to be able to define a product... But if you need to create a Product on the checkout page it would make more sense to define it in the context of a ProductTransaction (ie. ProductTransaction accepts product_attributes or product_id) instead of the context of a Transaction.
I came across a peculiar problem with has_one association in combination with an Object method override. Can somebody explain to me what is going on?
Here is how it goes:
I have a has_one relationship between Supplier and Account, like in the example of the has_one example used in Rails Guides.
Supplier:
class Supplier < ActiveRecord::Base
validates :name, :presence => true
has_one :account
nilify_blanks
end
Account:
class Account < ActiveRecord::Base
belongs_to :supplier
validates :supplier_id, :presence => true
nilify_blanks
def foo
puts 'in account'
end
def to_s
puts 'in account'
end
end
I also have a method foo on Object as follows:
class Object
def foo
puts 'in object'
end
end
When I call:
Supplier#account#to_s
I get 'in account'
When I call:
Supplier#account#foo
I get 'in object'
whereas I would expect it to print 'in account'
Does anybody have any clue why does this happen? Is this a bug in Rails ActiveRecord?
Thanks in advance
P.S. If you want, you can get a full fledged application that demonstrates the problem from here:
https://github.com/pmatsinopoulos/test_association_and_object_method_override.git
After doing some investingation with one of my friends, got the way assoiciation works.
when we do Supplier.account it will give you object of AssociationProxy not an object of account.
AssociationProxy delegates all methods to associated object if its definition not present in itself(it also delegates methods like class, inspect etc. so you can get the actual class name).
Now, when we add foo in Object class its available in AssociationProxy and when you say Supplier.account it invokes foo from AssociationProxy not from account.
if you want to invoke foo from account use target method to get actual account object like
Supplier.account.target.foo #=> foo from account
I have a parent model Account with multiple subclasses using STI. I want to associate another model Transaction using a belongs_to relationship to Account. The referenced account could be either an Asset or a Liability.
class Account < ActiveRecord::Base end
class Asset < Account end
class Liability < Account end
My transaction model belongs_to Account
class Transaction < ActiveRecord::Base
belongs_to :account #Either an Asset or Liability model
end
I want to be able to set a transaction's account to either an Asset or a Liability. However, I get a TypeMismatch error when I set the transaction's account to an Asset or Liablity since it isn't the parent class Account.
Note: I think this could be solved using polymorphism on the belongs_to association, but it seems unnecessary to specify the class in a type column when the referenced models all use the same underlying table.
It turns out that this code works as is. You don't need to specify a polymorphic type on the associated model to the STI model.
The type mismatch error I was receiving was because my STI base class was actually "Account::Base" and I simply had "Account" as the class name.