Field with type Array problem

14 views
Skip to first unread message

millisami

unread,
Jun 10, 2010, 3:58:19 PM6/10/10
to pickle
Hi, the following is my model:

class User < ActiveRecord::Base

#if new role is needed, then insert at the last, NOT IN THE MIDDLE
ROLES = %w[super_admin vendor_admin client_admin
client_trade_officer]

def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r|
2**ROLES.index(r) }.sum
end

def roles
ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
end

And this is my scenario:

Scenario: Vendor sends the proposal for the new campaign
Given a user "vendor" exists with roles: "vendor_admin"

When run this feature, it blows up:

Scenario: Vendor sends the proposal for the new campaign # features/
campaigns.feature:30
Given a user "vendor" exists with roles: "vendor_admin" # features/
step_definitions/pickle_steps.rb:4
undefined method `&' for "vendor_admin":String (NoMethodError)
./app/models/user.rb:18:in `roles='
./features/step_definitions/pickle_steps.rb:5:in

I think this is due to the type of the data for roles method. Since,
my roles= setter method expects an array, this might be the reason.
How can I send the roles attribute as an array instead of string that
pickle parser use?

millisami

unread,
Jun 14, 2010, 6:24:29 PM6/14/10
to pickle
I'm stuck with this.
Can anyone give me some direction on how to tackle it?

nruth

unread,
Jun 15, 2010, 7:50:25 AM6/15/10
to pickle
You could rewrite or add to your code to make it suit the tests.
Writing testable code isn't unheard of in agile.

You might like to look at named blueprints in machinist, if you made
User.blueprint(:vendor_admin) you could go Given a vendor admin user
exists.

Is this to use with STI?
I'd like it if we could go

Given a vendor admin exists

and then be able to talk about "the vendor admin" (as we can now) as
well as "the user" (as we can't, it doesn't get stored under that
ref). But haven't got round to patching pickle to do it.

nruth

unread,
Jun 16, 2010, 4:40:31 PM6/16/10
to pickle
I see I put my foot in my mouth there, sorry.

When I said "Writing testable code isn't unheard of in agile" I meant
that sometimes code gets written differently or refactored just so
that testing it is easier.

I'm not sure what your code is doing, but again you may be better off
using a custom step definition or named blueprint to set up with the
array as you want to. To pickle the model for use later in the
scenario you could try something like this:

Given /^a vendor exists$/ do
vendor = User.make :role => ['vendor_admin']
steps %Q(Then a user should exist with id: #{vendor.id})
end

You'll probably need to tweak this though, as I'm not sure what your
code is doing so may have the make wrong. If you aren't using
machinist substitute make with create or whichever factory method
fits.

HTH

Nick

millisami

unread,
Jun 17, 2010, 10:07:16 AM6/17/10
to pickle
Thanks, for the reply and suggestion.
But I've been doing that way with factory girl and its working and I
am using it. e.g.
Given I am logged in as vendor admin
or
Given I am logged in as client admin

Here is some different scenario involving various models.
Actually I showed only the few parts of the app, the question was not
clear.

Preface:
User - 2 types of users (vendor and client)

Roles - Different roles based upon the user belongs_to to either
Vendor or Client like
(vendor_admin, client_admin, client_manager, client_officer,
etc ...).
NOTE: It is setup using `embedded association with bit mask`
technique from
http://railscasts.com/episodes/189-embedded-association
The user model is shown below.

Campaign - Belongs to Vendor AND Client (Using has_many :through
between Vendor and Client)

I am using FactoryGirl and its as follows:

Factory.define(:client) do |c|
c.name { Faker::Company.name }
end
Factory.define(:vendor) do |v|
v.name { Faker::Company.name }
end
Factory.define :user do |user|
user.email { Factory.next :email }
user.password { "secret" }
user.password_confirmation { |u| u.password }
end
Factory.define :vendor_admin, :parent => :user do |u|
u.roles { %w[vendor_admin] }
u.association(:vendor)
end
Factory.define :client_admin, :parent => :user do |u|
u.roles { %w[client_admin] }
u.association(:client)
end
Factory.define :campaign do |c|
c.name { 1.week.ago }
c.start_date { 1.week.from_now }
c.end_date { 3.weeks.from_now }
c.association(:client)
c.association(:vendor)
end

For scenerios like following, I've a custom step def. For eg:
Scenario: Client users cannot create new campaign
Given I am logged in as client admin
When I go to the dashboard page
Then I should not see "Create a new campaign"
When I go to the new campaign page
Then I should see "You are not authorized to access the feature!"

I've my custom step as follows:
Given /^I am logged in as #{capture_model}$/ do |model_name|
user = Factory(model_name.to_s.gsub(/ /,"_").to_sym, :password =>
"secret")
When %{I login with "#{user.email}" with password "secret"}
Then %{I should see "Signed in successfully."}
end

And this works, using the factory :vendor_admin, perfectly. And it
just involves the login of single user i.e. vendor_admin or
client_admin at a time.

But now in case of Campaign, it involves campaign, vendor, client and
2 users (one as vendor with certain roles and one as client with
certain roles each)

So, now my real question is the following scenerio:

Scenario: Vendor sends the proposal for the new campaign
Given a user "vendor" exists with roles: "vendor_admin"
Given a vendor: "ndms" exists
And a client: "uniliver" exists
And a campaign exists with client: client "uniliver", vendor:
vendor "ndms"
And the vendor user is logged in
When I go to the campaign page
Then I should see "Propose Campaign"

All are fine except the first Given statement. I've setup users and
roles, as said above, using embedded association, so the roles= are
just the methods on the same user model, not a separate role model.

class User < ActiveRecord::Base
belongs_to :client
belongs_to :vendor

#if new role is needed, then insert at the last, NOT IN THE MIDDLE
ROLES = %w[super_admin vendor_admin client_admin
client_trade_officer]


def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r|
2**ROLES.index(r) }.sum
end

def roles
ROLES.reject { |r| ((roles_mask || 0) &
2**ROLES.index(r)).zero? }
end

Due to this roles methods, the pickle step cannot parse or understand
this roles method which is not set as association methods, they are
simply the methods of the model User itself.

...
undefined method `&' for "vendor_admin":String (NoMethodError)
./app/models/user.rb:18:in `roles='
...

There might be other ways out but I haven't tried coz I want the
handle for the particular User with certain role, not just log in, so
that I can assign it to the campaigns, log him in/out and the same for
the client type User as well.

I think I've made it clear. I am trying this in Pickle and only Pickle
coz it gives me (almost) generic steps, with object handles that can
be inferred-to with other associated objects.

Is there any clean and picklish way around??
Reply all
Reply to author
Forward
0 new messages