How to set Mongoid fields from within the class - ruby-on-rails-3

I am suddenly completely lost with scope of variables in Rails with Mongoid. (Probably due to a lack of coffee).
All I want, is a way to set certain fields from within the application, but the only way I can find to do this, is by calling write_attribute.
class Example
include Mongoid::Document
field :foo
def bar
#foo = "meh"
end
def hmpf
foo = "blah"
end
def baz
write_attribute(:foo, "meh")
end
end
e.bar #=> "meh"
e.foo #=> nil
e.hmpf #=> "blah"
e.foo #=> nil
e.baz #=> [nil, "meh"]
e.foo #=> "meh"
Am I using the scope wrong? Why will running foo = "bar" not set the field from within, it works from outside: e.foo = "blah" works trough the magic methods.

Try adding self to your attribute references when working in your model's instance methods:
def hmpf
self.foo = "blah"
end
Should do the trick.

Related

check instance variable value in before filter rspec

In my controller I have a method which checks for next item. But before executing this method I need to set an instance variable which is written in the before_filter method. So how do I test it in rspec.
before_filter method check_items, :only[:next]
def check_items
if params[:item] == "Sherlock"
#item = $book1
elsif params[:item] == "Harry"
#queue = $book2
else
render :json=>{"Error" => "Book name does not exist"}
end
end
=======================
def next
#book = #item.pull
unless #book.nil?
respond_with(#book)
else
render :json =>{"msg"=>"Nothing to pull"}
end
end
If you are writing controller specs, you can test this behavior with assigns constraint. You may end up with something like this:
it "assigns #item" do
item = Item.create
get :next
expect(assigns(:item)).to eq(item)
end
If you write unit tests, you can use instance_variable_get method. But I would avoid it - I would go with controller specs.
Hope that helps

Rails - avoid autosave in association

My models and its associations are:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
validates :commenter, :presence => true
end
Case1: Automatically save method is called when I tried below code.
#post = Post.find(3)
#comments = #post.comments
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{:commenter=>["can't be blank"]}
Case2: if I manually initialize same empty array to the #comments, auto save is not calling. for instance,
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
#comments = []
p #comments #=> []
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{}
What is the best solution to avoid auto save and please any one explain why the above code behave differently?
Rails extensively uses monkey-patching, so rails Array is not the same thing as pure Ruby array. (Compare output from irb > [].methods and rails c > [].methods
According to the documentation << method of has_many collection
instantly fires update sql without waiting for the save or update call
on the parent object
So most likely Rails have an "observer" of the collection events, and fires validation when you try to add new object.
In second snippet you use pure array (not has_many collection), so the update action is not fired.
To avoid update action you don't need << at all
#post = Post.find(3)
#comments = #post.comments
if #comments.empty?
3.times do
#post.comments.build
end
end
p #comments.size
=> 3
Autosave is defined in the Post model. Read here about Autosave. If I understand your question correctly, then it should be enough to define :autosave => false.

Rails 3: As json with include option does not takes into account as_json redefinition for included association

I've got two models.
Class ModelA < ActiveRecord::Base
has_many :model_bs
end
Class ModelB < ActiveRecord::Base
belongs_to :model_a
def as_json(options = {})
{
:whatever => 'hello world'
}
end
end
When I call model_a.as_json(:include => :model_b), I want it to return a json which includes all model_bs, which it does, but employing my as_json redefinition, which it does not as it just uses the default one. Is there any way to use my own method rather than the original one? Thanks
In Rails 3, as_json method invokes serializable_hash to obtain the attributes hash. And they share the same 'options' parameter. In your case, overwritting serializable_hash would give the expected result.
def serializable_hash(options = {})
{:whatever => 'hello world'}
end
But, My suggestion is that instead of overwriting the convention, operate on the result of "super", which is like:
def serializable_hash(options = {})
hash = super
has[:name] = "hello world"
hash
end

FactoryGirl: why does attributes_for omit some attributes?

I want to use FactoryGirl.attributes_for in controller testing, as in:
it "raise error creating a new PremiseGroup for this user" do
expect {
post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
}.to raise_error(CanCan::AccessDenied)
end
... but this doesn't work because #attributes_for omits the :user_id attribute. Here is the difference between #create and #attributes_for:
>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}
Note that the :user_id is absent from #attributes_for. Is this the expected behavior?
FWIW, my factories file includes definitions for :premise_group and for :user:
FactoryGirl.define do
...
factory :premise_group do
sequence(:name) {|n| "PremiseGroup_#{n}"}
user
is_visible false
is_open false
end
factory :user do
...
end
end
Short Answer:
By design, FactoryGirl's attribues_for intentionally omits things that would trigger a database transaction so tests will run fast. But you can can write a build_attributes method (below) to model all the attributes, if you're willing to take the time hit.
Original answer
Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes that strips id, created_at, and updated_at:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
end
So now:
>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}
... which is exactly what's expected.
update
Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes approach shown above should work.
I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.
Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.
Say your factory is like so:
factory :something
lat_d 12
lat_m 32
..
long_d 23
long_m 23.2
end
fearless's build_attributes would return { lat: nil, long: nil}. While the build_attributes below will return { lat_d: 12, lat_m: 32..., lat: nil...}
def build_attributes
ba = FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
af = FactoryGirl.attributes_for(*args)
ba.symbolize_keys.merge(af)
end
To further elaborate on the given build_attributes solution, I modified it to only add the accessible associations:
def build_attributes(*args)
obj = FactoryGirl.build(*args)
associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
accessible = obj.class.accessible_attributes
accessible_associations = obj.attributes.delete_if do |k, v|
!associations.member?(k) or !accessible.include?(k)
end
FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
Here is another way:
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:
A clean version for the most basic functionality for Rails 5+
This creates :belongs_to associations and adds their id (and type if :polymorphic) to the attributes. It also includes the code through FactoryBot::Syntax::Methods instead of an own module limited to controllers.
spec/support/factory_bot_macros.rb
module FactoryBot::Syntax::Methods
def nested_attributes_for(*args)
attributes = attributes_for(*args)
klass = args.first.to_s.camelize.constantize
klass.reflect_on_all_associations(:belongs_to).each do |r|
association = FactoryBot.create(r.class_name.underscore)
attributes["#{r.name}_id"] = association.id
attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
end
attributes
end
end
this is an adapted version of jamesst20 on the github issue - kudos to him 👏

Rails3/Mongoid - save model to lowercase

I'm using Rails 3.1 and Mongoid. What would be the proper way to enforce that a field of my model is saved to lowercase? I don't see this in the Mongoid documentation but I was wondering if there is a clean way I should know about. Thanks much.
Ok so I read the documentation more thoroughly, which I should have done initially. And this works for me now.
in the model.rb:
...
before_create :drop_the_case
protected
def drop_the_case
self.MYMODELFIELD = self.MYMODELFIELD.downcase
end
"drop_the_case" being my own arbitrary name for this.
Thanks.
In your model you can use
def before_save
self.your_model_field = your_model_field.downcase
end
or
def before_save
self.your_model_field.downcase!
end
Take a look at http://www.ruby-forum.com/topic/109091 This should work !!
The accepted answer with the before_create callback has some big issues, especially if you use certain constraints like validates_uniqueness_of. Use the before_validation callback instead when possible.
class Safe
include Mongoid::Document
field :foo, type: String
validates_uniqueness_of :foo
before_validation :drop_the_case
protected
def drop_the_case
self.foo = self.foo.downcase
end
end
class Dangerous
include Mongoid::Document
field :foo, type: String
validates_uniqueness_of :foo
before_create :drop_the_case
protected
def drop_the_case
self.foo = self.foo.downcase
end
end
dangerous = Dangerous.create!(name: 'BAR')
safe = Safe.create!(name: 'BAR')
dangerous.update(name: 'BAR') # dangerous.name => BAR
safe.update(name: 'BAR') # safe.name => bar
Dangerous.create!(name: 'BAR') # => true, unique constraint ignored
Safe.create!(name: 'BAR') # throws exception