Background
My application defines a one-to-many relationship between Employee and Company models. I've assigned the Employee fixture a company using the label of the Company fixture (37_signals). However, I also need to assign the company_uuid, which is generated by SecureRandom.uuid.
Example
app/models/
employee.rb
class Employee
belongs_to :company
end
company.rb
class Company
has_many :employees
end
test/fixtures/
employees.yml
employee:
name: dhh
company: 37_signals
company_uuid: <%= "Access the 37_signals company fixture's uuid here!" %>
companies.yml
37_signals:
name: $LABEL
company_uuid: <%= SecureRandom.uuid %>
Question
How can I access the attribute of a Fixture in another FixtureSet?
Attempted
I've attempted to use the following lines as solutions:
company_uuid: <%= ActiveRecord::FixtureSet.identify(:37_signals).company_uuid %>
The above finds the company's primary key id value, then calls the company_uuid method, which is undefined for the integer. This is invalid.
company_uuid: <%= companies(:37_signals).company_uuid %>
The above finds reports undefined method 'companies' for main:Object
Is there a conventional way to solve this problem?
Hmmm, the first option should be to DRY out the data, so it only exists in one place. You could do this with a delegate so that employee.company_uuid will always be answered by employee.company.company_uuid.
If you really need it in the Employee model, the next best choice would be to use a callback (like before_validate or after_save depending on your use-case), that copies the value from the Company to the Employee object. You want to eliminate chances for the data value to diverge from what it's true source should be.
Finally, you could extract all the UUIDs into a hash accessible to both fixtures at the time of creation, and set both values like:
company_uuid: <%= UUIDs['37_signals'] %>
...or similar
This is the best solution I've been able to devise:
company_uuid: <%= Company.find(ActiveRecord::FixtureSet.identify(:publish_and_export_album)).company_uuid %>
However, this solution does not seem conventional. Also, I believe that this succeeds with some luck since fixtures are loaded alphabetically. If Company was named Organization, loading after Employee, then I think this would not work as intended. Actually, through trial and error I determined that this method works in the opposite direction, so the alpha-order has no detrimental effect.
This isn't an issue if you use the fixture_builder gem. It allows you to build a graph of model objects and give them fixture names, and saves them to .yml fixture files for you.
Related
I have two interdependent models, account and user. An account is always created by a user, whose id is thus stored in the account's creator_id attribute, and a user necessarily belongs to an account (but there's no limit on the number of users belonging to an account), this information being stored in user's account_id attribute. The same user can have created different accounts.
I expressed these rules this way :
User model :
belongs_to :account, inverse_of: :users
has_many :created_accounts, class_name: "Account", :foreign_key => "creator_id"
Account model :
belongs_to :creator, class_name: 'User', optional: true
has_many :users, inverse_of: :account
As they are interdependent, I used a dirty workaround to be able to instantiate them : I create first the account, and following that action I force the user to create their profile, and their user_id is added to the account as creator_id in an update.
That's why I have, in Account model :
validate :require_actual_creator_id, on: :update
------------------------------------------------
def require_actual_creator_id
User.find(creator_id)
end
I was working on an authentication system only involving the user model, so I had these lines commented out until yesterday when I uncommented them.
I ran db:migrate:reset, db:seed and db:migrate RAILS_ENV=test without any problem, both models have a normal behavior in the console, but when it comes to fixtures (e.g. testing or db:fixtures:load), I got the following error :
NoMethodError: undefined method `id' for nil:NilClass
/home/vincent/workspace/bam-rails/test/fixtures/users.yml:16:in `get_binding'
Here is one typical fixture causing the problem, the line 16 being the commented one :
michael:
id: 1
handle: Michael Example
email: michael#example.com
encrypted_password: <%= User.generate_encrypted_token('password') %>
role_id: <%= User::ADMIN %>
is_activated: true
activated_at: <%= DateTime.now %>
#account_id: <%#= Account.first.id %>
When I comment this last line, there's no problem anymore. However, I'd like to load proper fixtures for my tests, because for example the user created here is not valid.
I read in this post that the fixtures load in the alphabetical order. If this is right, I can't get why I have to comment this line, because accounts is supposed to be loaded before users.
I found that solution to work but it's not from the official documentation and it's quite old, dated back to 2007. I am afraid this would stop working from one day to the next.
Does anyone know how to properly load the fixtures in a custom order in Rails 5, or has another solution to my problem ?
Thank you in advance.
The problem you have is entirely stemming from how you organized your code. That work around where you create the first account is where you are having an issue. So the fact that by the time your user is instantiated your account does not exist first because the fixtures are not loaded yet; of this I am sure you are aware. Fixtures are notoriously brittle this is why people often move away from them the more complex their code gets. In this case though the are helping you expose a code smell, anytime the order or running you test or basic non test case specific set up causes issues that means you have an issue with your code. I would suggest you find a way around using this "dirty work around".
Now if for some reason you are married to the way your code is currently organized I suggest maybe switching to factory girl, it will give you a little more flexibility to control the point at which your mock objects are instantiated that way you wont run into this issue. I will however say this will just enable you to continue you down this path that will more than likely just lead to more issues down the road, your best bet is to reimplement the feature.
This is probably a really simple question, but I've been searching the web for probably around an hour and I can't really find an answer to my problem. It should be clear by what follows that I am very new to Rails, so my terminology and explanation might be a bit confusing.
Let's say that I were making a social media app on Rails, where one of the models is User. I want to make a many-to-many relationship called "friends", which links two users together. Let's say in this situation I also wanted to make a many-to-many between two users called "enemies".
This is all completely hypothetical, but the idea is the same one that I want to use for something I'm working on.
Because a user can have many friends and enemies, but also be many friends and enemies, I would use:
class User < ActiveRecord::Base
has_and_belongs_to_many :users #this should be the friends association
has_and_belongs_to_many :users #this should be the enemies association
end
Now I'm guessing I can't just do that, because I would have to have two tables both named users_users. So, then I switch to:
class User < ActiveRecord::Base
has_and_belongs_to_many(:users, join_table: 'friends',
foreign_key: 'user_id', associate_foreign_key: 'friend_id')
end
With a similar statement for the enemies table. Now, my problem is that I want to have a form that the user can use when they sign up, where they can input their information (this is the User object details), and also list their friends and enemies.
Because the user won't have the database id key for their friends or enemies, they'll have to input the users' names. This is fine, though because the name is also a unique key, guaranteed by the validation.
However, if the user types in the name of a friend, I can't join the two if the friend happens to not exist. So, I use a custom validation class that looks something like this:
class FriendValidator < ActiveModel::Validator
def validate(object)
#lookup user and throw error if not found.
end
end
which will access the variable (object.friends) and (object.enemies)
With something similar for enemies. So therefore, above my has_and_belongs_to_many statements, I have lines that say:
attr_accessor :friends, :enemies #these are attrs because they don't exist within the model's db
validates_with FriendValidator
When I create the form with erb, I have the standard form_for block
<%= form_for(#user) do |f| %>
It seems to me that I can't just stick
<%= f.text_area :friends %>
because friends isn't actually something that will get passed to the User object, but rather a separate table. (Can I, though? Because the attr_accessor is declared in the user's model class?)
So now, we have my main problem. I have two many-to-many tables with a model to its own model class, and I don't know how to ensure that the validation class will take the two attributes, lookup and throw necessary errors, and then add a row to the join tables using the id of the user, rather than the string inputted. What form fields should I use to pass the input to the right place? Where do I change the controller methods so that the input gets sent to the join table rather than the user object?
This definitely seems like a pretty specific situation, so I can't really find an answer in the Rails documentation, which I've been learning from.
My initial impression of this problem has to do with your associations. To me, a user has_many enemies and has_many friends.
friends belong_to user
enemies belong_to user
Not sure if a many to many relationship makes sense in this case. Maybe that's why you are having such a hard time finding an answer online. Just my two cents.
I have a Review model. My users should be able to write reviews. The view for the Review#New should be a form with textfields that the admin creates beforehand.
In other words, my admin-user should be able to create multiple instances of a Review model that has different fields, perhaps even of different input types (string, integer, etc.). That way, when a regular user logs in, they see the different form fields that were specified for data collection by the admin user.
Naturally all of that should be stored in the DB for retrieval within the context it was stored (aka for that specific model).
What's the best way to approach this in Rails?
Think of it like a survey form, and a survey form builder.
It would be good if I could do this with Simple-Form, but that's not a requirement.
Edit 1
Here is an example of the type of fields that they should be able to add to a review:
In my experience a good portion of database design is helped by simply finding the right name for things. In your case I think you are on the right track with thinking about surveys or quizzes.
Check out the survey gem for ideas. In it the base model is Surveys. Surveys have many Questions. Questions have many Options. Surveys also have many Attempts which are answered surveys. Attempts then have many Answers.
So the corollary for you could be to have Reviews/Evaluations (created by admins) which might have many Criteria/Inquiries (possibly of different types, but we'll get to that in a minute). Then your users would create Responses/Assessments which would belong to a specific Review/Evaluation and have many Answers/Responses.
For different question types (Short Answer, Likert Scale Rating, 1-10, Tag List, etc) you could use polymorphism on the criteria/inquiries.
Hopefully some of these names I've used will help you. Feel free to use a thesaurus for more inspiration.
EDIT Re:Polymorphism
Disclaimer: polymorphism might be overkill depending on your application.
Sure, I'll expand some. Not exactly. Take a look at the rails guide on polymorphism if you haven't already. I think what you would want is
class Criterion < ActiveRecord::Base
belongs_to :askable, polymorphic: true
end
Then then I would make a model for each question/criterion type. For example:
class ShortAnswer < ActiveRecord::Base
has_many :criteria, as: :askable
end
class Likert < ActiveRecord::Base
has_many :criteria, as: :askable
end
Side note: If rails does not properly pluralize criterion to criteria you may need to add the following to your config/initializers/inflections.rb file
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'criterion', 'criteria'
end
Scratch solution.
From my experience the easiest solution is to use hstore, json or jsonb type of fields.
This solution play good with Postgresql database.
To achieve this approach you need to add field to your Review model.
Migrations:
# Reviews
def change
add_column :reviews, :structure, :json
end
# Answers
def change
add_column :answers, :values, :hstore
end
Then you can define model ReviewStructure plain ruby class, here you can use Virtus gem to serialize it easely:
class ReviewStructure
include Virtus.model
attribute :fields, Array[Field]
class Field
include Virtus.model
attribute :name
attribute :type
end
end
Then define in Review the serialization for structure field:
class Review < ActiveRecord::Base
...
serialize :structure, ReviewStructure
end
Then you can access structure fields of review with review.structure.fields.
In view you can use a simple form
<% simple_form_for #answer do |f| %>
<% #review.structure.fields.each do |field| %>
<% f.input "values[#{field.name}]", as: field.type %>
<% end %>
<% end %>
To access answer results just use:
answer.values.each do |field_name, value|
...
end
Note:
As for admin for it's better to handle creation of review structure on client side(using js), and post pure JSON structure via API.
With such approach you will have ability to create quizzes with different types of field.
Note:
Please keep in mind that current implementation connect one review to one answer, assuming that the answer model contains all the values of user response.
I am confused about the some Association concepts in Active Records.
I have three models User, Bank and Bankaccount. Both the User and the Bank models "has_many" Bankaccounts and the Bankaccount model "belongs_to" both the User and the Bank models. I use the following syntax to create a Bankaccount through its association with User
#bankaccount = #user.bankaccounts.create(bankaccount_params)
What is the appropriate syntax if I want to create a bankaccount object through both the association with User and the association with Bank?
My second question is related to this one. Right now, because I am not sure how to create a bankaccount through both associations, I handle the association with the Bank by putting the parameter manually
bank_id = params[:bank_id]
However, this seems to trigger some issues down the road when I want to iterate through all the bankaccounts and retrieve the name of the associated bank.
In my view I have
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank %>
I obtained a list of these
#<Bank:0x007f7a66618ef0>
#<Bank:0x007f7a664c9ab8>
If I tried to get the name of the bank
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank.name %>
I get an undefined method name for nil class. I do get the name of the bank in the console with these simple lines
bankaccount = Bankaccount.find(1)
bankaccount.bank.name
Could you anyone give me more background on those concepts and provide me with the appropriate syntax to loop accross my collection #user.bankaccount and for each bankaccount retrieve the name of the associated bank?
Thanks.
You'll have to choose one association to create a bankaccount through, then set the second separately:
#bankaccount = #user.bankaccounts.new(bankaccount_params)
#bankaccount.bank = somebank
#bankaccount.save
Or
#bankaccount = #bank.bankaccounts.new(bankaccount_params)
#bankaccount.user = someuser
#bankaccount.save
In addition, I don't see why setting the second association manually with a param would inherently cause the other problems you are experiencing. This should be fine (assuming a bank with this id actually exists):
#bankaccount.bank_id = params[:bank_id]
If you choose to assign a foreign key as a parameter, you can roll it into strong parameters and pass it into the bankaccount model with everything else. For example:
def bankaccount_params
params.require(:bankaccount).permit(:bank_id, ...)
end
You last issue regarding arrays vs. collections depends on what you are trying to do. First, if you are particularly interested in the bankaccount's bank name, make it easier to get:
class Bankaccount
belongs_to :bank
...
def bank_name
bank.name
end
end
For those who buy into such things, this also prevents a Law of Demeter violation.
If you are simply trying to list the names of banks for #bankaccounts in a view, try leveraging Rails partials with something like this:
app/views/bankaccounts/index.html.erb
<%= render #bankaccounts %>
app/views/bankaccounts/_bankaccount.html.erb
<%= bankaccount.bank_name %>
More on this here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
If you're looping over #bankaccounts for another reason, the code you provided should work, given that #bankaccounts represents ActiveRecord relations and not a simple array:
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank_name %>
<% end %>
Since you're getting an undefined method error, your problem probably stems from how you are building #bankaccounts. If you are doing exactly this...
#bankaccounts = #user.bankaccounts
...and you've verified that everything is properly associated in the console, then your problem is likely unrelated to arrays or collections.
Lets say I have a working form that looks like the following
=form_for #survey do |f|
=f.text_field :name
=f.fields_for :questions do |question_fields|
=question_fields.text_field :question_text
=question_fields.fields_for :answers do |answer_fields|
=answer_fields.text_field :answer_text
Because different parts of the form can be added and updated by different users I need a way to get the user_id into each model before it is saved. I realize it is not mvc compliant to be able to access current_user inside the model, that being said I am left without a solution.
If I was only saving one object it would be simple enough to assign the current_user.id to the object in the controller, but given the deeply nested nature of this form that starts to look like an ugly solution.
Is there an expert/railsy way to handle this?
Rails 3.2, devise
Can't each of the objects simply steal the user_id from their "parent" relationship? This is a common pattern:
class Answer < ActiveRecord::Base
before_validation :assign_user_id
protected
def assign_user_id
# Don't attempt if the question is not defined,
# or the user_id field is already populated.
return unless (self.question or self.user)
self.user_id = self.question.user_id
end
end
This involves a bit of additional database activity to resolve the answer for each question, as creating it in a scope is not sufficient, but it makes it pretty much fool-proof.
What you probably want to do is stuff in the user_id parameter when creating each record. This means your create call needs to merge in a :user_id key where required. The nested helper doesn't do this by default, though, so if you're using that you may just leave it up to the assign method.