diff --git a/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee new file mode 100644 index 0000000000..1fecfbf804 --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee @@ -0,0 +1,13 @@ +Darkswarm.directive "ofnRegistrationLimitModal", (Navigation, $modal, Loading) -> + restrict: 'A' + link: (scope, elem, attr)-> + scope.modalInstance = $modal.open + templateUrl: 'registration/limit_reached.html' + windowClass: "login-modal large" + backdrop: 'static' + + scope.modalInstance.result.then scope.close, scope.close + + scope.close = -> + Loading.message = "Taking you back to the home page" + Navigation.go "/" diff --git a/app/assets/javascripts/darkswarm/filters/show_profiles.js.coffee b/app/assets/javascripts/darkswarm/filters/show_profiles.js.coffee index 32e4438e82..e3fce7d2f6 100644 --- a/app/assets/javascripts/darkswarm/filters/show_profiles.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/show_profiles.js.coffee @@ -4,4 +4,4 @@ Darkswarm.filter 'showProfiles', ()-> show_profiles ?= true enterprises.filter (enterprise)=> - show_profiles or not enterprise.is_profile + show_profiles or enterprise.has_shopfront diff --git a/app/assets/javascripts/templates/registration/limit_reached.html.haml b/app/assets/javascripts/templates/registration/limit_reached.html.haml new file mode 100644 index 0000000000..e2131c1727 --- /dev/null +++ b/app/assets/javascripts/templates/registration/limit_reached.html.haml @@ -0,0 +1,15 @@ +%div + .header.center + %h2 Oh no! + %h4 You have reached the limit! + .row + .small-12.medium-3.large-2.columns.text-right.hide-for-small-only + %img{:src => "/assets/potatoes.png"} + .small-12.medium-9.large-10.columns + %p + You have reached the limit for the number of enterprises you are allowed to own on the + %strong Open Food Network. + .row + .small-12.columns + %hr + %input.button.primary{ type: "button", value: "Return to the homepage", ng: { click: "close()" } } diff --git a/app/assets/stylesheets/darkswarm/branding.css.sass b/app/assets/stylesheets/darkswarm/branding.css.sass index 011ef2ff68..0fa559240f 100644 --- a/app/assets/stylesheets/darkswarm/branding.css.sass +++ b/app/assets/stylesheets/darkswarm/branding.css.sass @@ -17,7 +17,9 @@ $clr-blue-bright: #14b6cc $disabled-light: #e5e5e5 $disabled-bright: #ccc +$disabled-med: #b3b3b3 $disabled-dark: #999 +$disabled-v-dark: #808080 $med-grey: #666 $dark-grey: #333 - +$black: #000 diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index eb539cc0b5..d02f9b5299 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -87,6 +87,8 @@ &.inactive &.closed, &.open &, & * + color: $disabled-med + a, a * color: $disabled-dark &.closed .active_table_row, .active_table_row:first-child, .active_table_row:last-child @@ -126,3 +128,13 @@ .active_table_row:first-child .skinny-head background-color: $disabled-light + //Is Profile - profile node + &.inactive.is_profile + &.closed, &.open + .active_table_row + &:hover, &:active, &:focus + border-color: transparent + cursor: auto + @media all and (max-width: 640px) + border-color: transparent + diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index f469066836..2ceaa96373 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -35,11 +35,7 @@ outline: 0 @media only screen and (max-width: 640px) padding-top: 0.5rem - padding-bottom: 0.35rem - - h3 - // Because this is only producers - color: $clr-turquoise + padding-bottom: 0.35rem h3, p margin-top: 0 diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb index 47a1537b57..4fae68bb79 100644 --- a/app/controllers/registration_controller.rb +++ b/app/controllers/registration_controller.rb @@ -20,6 +20,8 @@ class RegistrationController < BaseController def check_user if spree_current_user.nil? redirect_to registration_auth_path(anchor: "signup?after_login=#{request.env['PATH_INFO']}") + elsif !spree_current_user.can_own_more_enterprises? + render :limit_reached end end end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 30a5939b08..ecd948aa7a 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -215,20 +215,15 @@ class Enterprise < ActiveRecord::Base # Type: full - single - profile becomes Sells: all - own - none # Remove this return later. return "none" if !is_distributor || type == "profile" - return "own" if is_distributor && (suppliers != [self] || type == "full") - "own" - end - - # New boolean field shows whether we can supply products into the system. - def supplies - is_primary_producer && type != "profile" #and has distributors? + return "own" if suppliers == [self] || type == "single" + "all" end # Simplify enterprise categories for frontend logic and icons, and maybe other things. def enterprise_category - # Make this crazy logic human readable so we can argue about it sanely. + # Make this crazy logic human readable so we can argue about it sanely. # This can be simplified later, it's like this for readablitlty during changes. - category = is_primary_producer ? "producer_" : "non_producer_" + category = is_primary_producer ? "producer_" : "non_producer_" category << "sell_" + sells # Map backend cases to front end cases. diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index d21cfecabb..6841337024 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -4,7 +4,7 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer end private - + def cached_serializer_hash Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash end @@ -18,7 +18,7 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active #TODO: Remove these later - attributes :icon, :icon_font, :producer_icon_font, :has_shopfront, :is_profile, :enterprise_category + attributes :icon, :icon_font, :producer_icon_font, :has_shopfront, :has_hub_listing, :enterprise_category def orders_close_at OrderCycle.first_closing_for(object).andand.orders_close_at @@ -36,13 +36,14 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer object.is_distributor && object.type != 'profile' end - def is_profile - object.sells == "none" && !object.supplies + # Used to select enterprises for hub listing + def has_hub_listing + has_shopfront || object.enterprise_category == "hub_profile" end # Map svg icons. def icon - icons = { + icons = { "hub" => "/assets/map_005-hub.svg", "hub_profile" => "/assets/map_006-hub-profile.svg", "producer_hub" => "/assets/map_005-hub.svg", @@ -67,7 +68,7 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer end # Choose producer page icon font - yes, sadly its got to be different. - # This duplicates some code but covers the producer page edge case where + # This duplicates some code but covers the producer page edge case where # producer-hub has a producer icon without needing to duplicate the category logic in angular. def producer_icon_font icon_fonts = { diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml index 3060681a33..ff13a0c33c 100644 --- a/app/views/home/_filters.html.haml +++ b/app/views/home/_filters.html.haml @@ -1,20 +1,22 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + = render partial: 'shared/components/show_profiles' .row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns + .small-12.columns .row.filter-box .small-12.large-9.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5 - %taxon-selector{objects: "hubs | hubs:query", + %taxon-selector{objects: "hubs | hubs:query", results: "activeTaxons"} .small-12.large-3.columns - %h5.tdhead + %h5.tdhead .light Filter by Delivery %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2 - %shipping-type-selector{results: "shippingTypes"} + %shipping-type-selector{results: "shippingTypes"} = render partial: 'shared/components/filter_box' diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index b80b9305a3..5a152fffb6 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -32,3 +32,4 @@ Sorry, no results found for %strong {{query}}. Try another search? + diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index d160b256f3..2010bffe1e 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,7 +1,7 @@ -.row.active_table_row{"ng-if" => "!hub.is_profile", "ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'has_shopfront' : producer.has_shopfront}", bindonce: true} +.row.active_table_row{"ng-if" => "hub.has_shopfront", "ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'has_shopfront' : producer.has_shopfront}", bindonce: true} .columns.small-12.medium-6.large-5.skinny-head - %a.hub{"ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} + %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i{ng: {class: "hub.icon_font"}} %span.margin-top.hub-name-listing {{ hub.name | truncate:40}} @@ -13,18 +13,18 @@ .columns.small-6.medium-3.large-4.text-right{"bo-if" => "hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_033-open-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ bo: { if: "current()" } } %em Shopping here %span.margin-top{ bo: { if: "!current()" } } {{ hub.orders_close_at | sensible_timeframe }} .columns.small-6.medium-3.large-4.text-right{"bo-if" => "!hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_032-closed-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ bo: { if: "current()" } } %em Shopping here %span.margin-top{ bo: { if: "!current()" } } Orders closed -.row.active_table_row{"ng-if" => "hub.is_profile", "ng-class" => "closed"} +.row.active_table_row{"ng-if" => "!hub.has_shopfront", "ng-class" => "closed"} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i{ng: {class: "hub.icon_font"}} @@ -36,4 +36,5 @@ %span.margin-top {{ hub.address.state_name | uppercase }} .columns.small-6.medium-3.large-4.text-right - %span.margin-top{ bo: { if: "!current()" } } Profile only + %span.margin-top{ bo: { if: "!current()" } } + %em Profile only diff --git a/app/views/producers/_filters.html.haml b/app/views/producers/_filters.html.haml index cd36389c8b..00472dc91c 100644 --- a/app/views/producers/_filters.html.haml +++ b/app/views/producers/_filters.html.haml @@ -1,13 +1,15 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + .small-12.medium-6.columns.text-right +   .row.animate-show{"ng-show" => "filtersActive"} .small-12.columns .row.filter-box .small-12.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6 %taxon-selector{objects: "Enterprises.producers | searchEnterprises:query ", results: "activeTaxons"} - diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index e012f4c1ee..371c873f64 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -1,18 +1,23 @@ +<<<<<<< HEAD = inject_enterprises .producers.pad-top{"ng-controller" => "EnterprisesCtrl"} +======= += inject_enterprises +.producers.pad-top{"ng-controller" => "ProducersCtrl"} +>>>>>>> master .row .small-12.columns.pad-top %h1 Find local producers - / %div - / Find a + / %div + / Find a / %ofn-modal{title: "producer"} / = render partial: "modals/producers" / from the list below: #active-table-search.row .small-12.columns - %input.animate-show{type: :text, - "ng-model" => "query", + %input.animate-show{type: :text, + "ng-model" => "query", placeholder: "Search by producer or suburb...", "ng-debounce" => "150", "ofn-disable-enter" => true} @@ -24,7 +29,7 @@ .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", "scroll-after-load" => true, - "ng-repeat" => "producer in producers = (Enterprises.producers | showProfiles:show_profiles | searchEnterprises:query | taxons:activeTaxons)", + "ng-repeat" => "producer in producers = (Enterprises.producers | searchEnterprises:query | taxons:activeTaxons)", "ng-controller" => "ProducerNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} diff --git a/app/views/registration/limit_reached.html.haml b/app/views/registration/limit_reached.html.haml new file mode 100644 index 0000000000..bfaec6da3d --- /dev/null +++ b/app/views/registration/limit_reached.html.haml @@ -0,0 +1,2 @@ +/ Directive which loads the modal +%div{ "ofn-registration-limit-modal" => true } diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index afeb37c856..e15d2de48d 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -1,16 +1,9 @@ -.row - .small-12.medium-6.columns - %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", - "ng-show" => "FilterSelectorsService.selectors.length > 0"} - {{ filterText(filtersActive) }} - %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} - %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} +.small-12.medium-6.columns + %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", + "ng-show" => "FilterSelectorsService.selectors.length > 0"} + {{ filterText(filtersActive) }} + %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} + %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} - %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} - No filters - .small-12.medium-6.columns.text-right - .profile-checkbox - %input{"ng-model" => "show_profiles", type: "checkbox", name: "profile"}>< - %label Show profiles - %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but do have their own physical or online shop elsewhere", "popover-placement" => "left"}>< - %i.ofn-i_013-help + %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} + No filters diff --git a/app/views/shared/components/_show_profiles.html.haml b/app/views/shared/components/_show_profiles.html.haml new file mode 100644 index 0000000000..5dc0c7c6dc --- /dev/null +++ b/app/views/shared/components/_show_profiles.html.haml @@ -0,0 +1,6 @@ +.small-12.medium-6.columns.text-right + .profile-checkbox + %input{"ng-model" => "show_profiles", type: "checkbox", name: "profile"}>< + %label Show profiles + %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but do have their own physical or online shop elsewhere", "popover-placement" => "left"}>< + %i.ofn-i_013-help diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 37f6ae5eda..c135274fad 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,14 +1,17 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + .small-12.medium-6.columns.text-right +   .row.animate-show{"ng-show" => "filtersActive"} .small-12.columns .row.filter-box .small-12.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-3.large-block-grid-4 - %taxon-selector{objects: "Products.products | products:query | products:showProfiles", + %taxon-selector{objects: "Products.products | products:query | products:showProfiles", results: "activeTaxons"} = render partial: 'shared/components/filter_box' diff --git a/spec/controllers/registration_controller_spec.rb b/spec/controllers/registration_controller_spec.rb index 13babdf89e..e51a28f73a 100644 --- a/spec/controllers/registration_controller_spec.rb +++ b/spec/controllers/registration_controller_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe RegistrationController do + include AuthenticationWorkflow describe "redirecting when user not logged in" do it "index" do get :index @@ -13,26 +14,38 @@ describe RegistrationController do end end - describe "loading data when user is logged in" do - let!(:user) { double(:user) } + describe "redirecting when user has reached enterprise ownership limit" do + let!(:user) { create_enterprise_user( enterprise_limit: 1 ) } + let!(:enterprise) { create(:distributor_enterprise, owner: user) } + + before do + controller.stub spree_current_user: user + end + + it "index" do + get :index + response.should render_template :limit_reached + end + end + + describe "loading data when user is logged in" do + let!(:user) { create_enterprise_user } before do controller.stub spree_current_user: user - user.stub spree_api_key: '12345' - user.stub last_incomplete_spree_order: nil end describe "index" do it "loads the spree api key" do get :index - expect(assigns(:spree_api_key)).to eq '12345' + expect(assigns(:spree_api_key)).to eq user.spree_api_key end end describe "store" do it "loads the spree api key" do get :store - expect(assigns(:spree_api_key)).to eq '12345' + expect(assigns(:spree_api_key)).to eq user.spree_api_key end end end diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index bfc624bce5..5e4366b6f7 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -89,7 +89,7 @@ feature %q{ let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } let!(:d3) { create(:distributor_enterprise) } - let(:enterprise_user) { create_enterprise_user([d1]) } + let(:enterprise_user) { create_enterprise_user( enterprises: [d1] ) } let!(:er1) { create(:enterprise_relationship, parent: d1, child: d2) } let!(:er2) { create(:enterprise_relationship, parent: d2, child: d1) } diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index e880f25d23..5228b4c8a0 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -10,8 +10,8 @@ feature %q{ context "Permissions for different reports" do context "As an enterprise user" do let(:user) do - create_enterprise_user([ - create(:distributor_enterprise) + create_enterprise_user(enterprises: [ + create(:distributor_enterprise) ]) end it "should not show the Sales Total report" do @@ -99,7 +99,7 @@ feature %q{ let(:shipping_instructions) { "pick up on thursday please!" } let(:order1) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) } let(:order2) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) } - + before do Timecop.travel(Time.zone.local(2013, 4, 25, 14, 0, 0)) { order1.finalize! } Timecop.travel(Time.zone.local(2013, 4, 25, 16, 0, 0)) { order2.finalize! } @@ -144,7 +144,7 @@ feature %q{ variant_2.update_column(:count_on_hand, 20) product_2.master.update_column(:count_on_hand, 9) variant_1.option_values = [create(:option_value, :presentation => "Test")] - + login_to_admin_section click_link 'Reports' @@ -165,4 +165,3 @@ feature %q{ end end end - diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 8c611081a2..7d9a7350b5 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -5,6 +5,7 @@ //= require angular-mocks //= require angular-cookies //= require angular-backstretch.js +//= require angularjs-file-upload //= require lodash.underscore.js //= require angular-flash.min.js //= require shared/mm-foundation-tpls-0.2.2.min.js diff --git a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee index f8ae8230bc..f2620da65b 100644 --- a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee @@ -1,46 +1,45 @@ describe "Hubs service", -> Hubs = null Enterprises = null - CurrentHubMock = {} + CurrentHubMock = {} hubs = [ { id: 2 active: false orders_close_at: new Date() is_distributor: true - has_shopfront: true + has_hub_listing: true } { id: 3 active: false orders_close_at: new Date() is_distributor: true - has_shopfront: true + has_hub_listing: true } { id: 1 active: true orders_close_at: new Date() is_distributor: true - has_shopfront: true + has_hub_listing: true } ] - + beforeEach -> module 'Darkswarm' - angular.module('Darkswarm').value('enterprises', hubs) + angular.module('Darkswarm').value('enterprises', hubs) module ($provide)-> - $provide.value "CurrentHub", CurrentHubMock + $provide.value "CurrentHub", CurrentHubMock null inject ($injector)-> - Enterprises = $injector.get("Enterprises") + Enterprises = $injector.get("Enterprises") Hubs = $injector.get("Hubs") it "filters Enterprise.hubs into a new array", -> expect(Hubs.hubs[0]).toBe Enterprises.enterprises[2] - # Because the $filter is a new sorted array + # Because the $filter is a new sorted array # We check to see the objects in both arrays are still the same - Enterprises.enterprises[2].active = false + Enterprises.enterprises[2].active = false expect(Hubs.hubs[0].active).toBe false - diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index a2ee597f27..39f78223d7 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -37,12 +37,9 @@ module AuthenticationWorkflow visit spree.admin_path end - def create_enterprise_user(enterprises = []) - new_user = create(:user, password: 'blahblah', :password_confirmation => 'blahblah') + def create_enterprise_user( attrs = {} ) + new_user = create(:user, attrs) new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default. - for enterprise in enterprises do - new_user.enterprise_roles.build(enterprise: enterprise).save - end new_user.save new_user end